From 2724643ba3b2f56668838298d3ac3022d02a1bb8 Mon Sep 17 00:00:00 2001 From: Javier Alvarez-Valle Date: Fri, 16 Apr 2021 13:02:18 +0100 Subject: [PATCH] Init --- .gitattributes | 4 + .github/workflows/build-test.yml | 64 + .gitignore | 365 + Build/azure-pipelines.yml | 62 + CODE_OF_CONDUCT.md | 9 + .../DeployResourcesAzureStackHub.md | 3 + Docs/Additional Documents/EndToEndDemo.md | 38 + .../EndToEndOnAzureStackHub.md | 10 + Docs/Additional Documents/InferencingAPIs.md | 56 + .../InferencingContainer.md | 3 + .../Additional Documents/InferencingEngine.md | 7 + .../MoreAboutInnerEyeProject.md | 3 + Docs/Additional Documents/Setup.md | 328 + Docs/Additional Documents/VideoAndBlogs.md | 3 + Docs/Diagram.md | 39 + Docs/Help and Bugs Reporting/BugReporting.md | 4 + .../PullRequestsGuidelines.md | 3 + .../Stack-overflowAndOtherChannels.md | 3 + Docs/environment.png | 3 + Docs/sequence.png | 3 + Docs/sequence.wsd | 59 + GeoPol.xml | 23 + Images/1ValidSmall/1.dcm | 3 + Images/1ValidSmall/10.dcm | 3 + Images/1ValidSmall/11.dcm | 3 + Images/1ValidSmall/12.dcm | 3 + Images/1ValidSmall/13.dcm | 3 + Images/1ValidSmall/14.dcm | 3 + Images/1ValidSmall/15.dcm | 3 + Images/1ValidSmall/16.dcm | 3 + Images/1ValidSmall/17.dcm | 3 + Images/1ValidSmall/18.dcm | 3 + Images/1ValidSmall/19.dcm | 3 + Images/1ValidSmall/2.dcm | 3 + Images/1ValidSmall/20.dcm | 3 + Images/1ValidSmall/3.dcm | 3 + Images/1ValidSmall/4.dcm | 3 + Images/1ValidSmall/5.dcm | 3 + Images/1ValidSmall/6.dcm | 3 + Images/1ValidSmall/7.dcm | 3 + Images/1ValidSmall/8.dcm | 3 + Images/1ValidSmall/9.dcm | 3 + Images/InvalidPN/1.dcm | 3 + ....1090460865966542526486876149643126065.dcm | 3 + ....1113583911936106161255831110435725437.dcm | 3 + ....1247728137096394998404446793947041469.dcm | 3 + ....1268297749850909254845172237211472094.dcm | 3 + ....1276409595806088158265421354038204673.dcm | 3 + ....1276823266322239590774784606318904757.dcm | 3 + ....1309493738860257695938496641148832585.dcm | 3 + ....1315228061378753091775225585475513904.dcm | 3 + ....1424821720661730651575121131022380064.dcm | 3 + ...3.149540744391437319866380830011665495.dcm | 3 + ....1504292588464778016131047913246200712.dcm | 3 + ....1586289423697072709053282986003052130.dcm | 3 + ....1603507555357901003873369762405395158.dcm | 3 + ....1603941769404672370986212481856666706.dcm | 3 + ....1733723835157503707471772250948338056.dcm | 3 + ...3.177159971429155354323352122369910018.dcm | 3 + ....2065039152027214948185192787016609102.dcm | 3 + ....2077707482707913294090849361486537575.dcm | 3 + ....2091056545198481824972740777538855628.dcm | 3 + ....2300191907444461623117367828109639362.dcm | 3 + ....2311513203284923046703814127142600862.dcm | 3 + ...3.234848398285035287627993400411403947.dcm | 3 + ....2366245601673390599661173419249430152.dcm | 3 + ....2517875113743844279077466488297548492.dcm | 3 + ....2528918310531862024240590111330526032.dcm | 3 + ...3.255472593933636444021718369425699674.dcm | 3 + ...3.272404100495827097094503485633903342.dcm | 3 + ....2856276318351938990833279631467510060.dcm | 3 + ...43.28621440201171685228804903937000813.dcm | 3 + ....2929087666691054004992797698806946100.dcm | 3 + ....2965520600218423570825909564893574418.dcm | 3 + ....3039200638329081880003952315701223755.dcm | 3 + ....3138565465547482227814490173133974101.dcm | 3 + ....3264163721230795368390441633568856169.dcm | 3 + ....3329214252684256799918043573009559701.dcm | 3 + ....3342450797385800689172373508778910683.dcm | 3 + ....3348092881200381409012471499478647143.dcm | 3 + ....3456195802144665971608371497453372482.dcm | 3 + ....3531577771862774762674919299088992667.dcm | 3 + ....3559519125404573210574469451974196707.dcm | 3 + ....3707432864349768500524190555289963771.dcm | 3 + ....3826387874733181325715780105257912381.dcm | 3 + ....3884266638195393445767846913412404340.dcm | 3 + ....3901472286358631291254773359964022615.dcm | 3 + ....4077627525916031544392875981537574880.dcm | 3 + ....4077765416632201049866028033810645876.dcm | 3 + ...3.416267245062537504426664884554455695.dcm | 3 + ....4220429893402217707626563486809748036.dcm | 3 + ....4227760063788177814850539832569856657.dcm | 3 + ....4361110441258480612761531589119880729.dcm | 3 + ....4429117347680799243274407179054009519.dcm | 3 + ....4430789403887947931838641862616347273.dcm | 3 + ....4545923077608497415467351747154540694.dcm | 3 + ...3.457099872952531787279666134580381486.dcm | 3 + ....4583195912092827698588179857276488969.dcm | 3 + ....4593469450985571044589247082565455469.dcm | 3 + ....4661186853222973408651088617727253235.dcm | 3 + ....4678179273222049229856606186006082767.dcm | 3 + ....4714785946536100308562944102363288971.dcm | 3 + ....4766066604650145714566023185265207574.dcm | 3 + ....4786946907986646372976065107744927432.dcm | 3 + ...3.479594740413779986935166935711464664.dcm | 3 + ....4820400408130735437856746766113162355.dcm | 3 + ....4847684476803386246306224608512829862.dcm | 3 + ....4848358862206565187800419652953160929.dcm | 3 + ....4867098704721969152550959342284226672.dcm | 3 + ....4974603962485423328908158546094390835.dcm | 3 + ....5032568314730441475611031612232702813.dcm | 3 + ....5117600506941670944922941183791284804.dcm | 3 + ...3.514204938254240720422969359500861281.dcm | 3 + ....5168782564514843969477858686535623503.dcm | 3 + ....5214260960761315974879308927152578120.dcm | 3 + ....5353352661842228977009124604457342114.dcm | 3 + ....5375120196677187954364257785394062354.dcm | 3 + ....5480675569475985780063595571287169751.dcm | 3 + ....5508569889704436607767594729119990566.dcm | 3 + ....5521222100884173015423683049999264236.dcm | 3 + ...3.560743097336335340588260575577289706.dcm | 3 + ....5639810407786340123860181536716326619.dcm | 3 + ....5692514868855490639520084349215228577.dcm | 3 + ....5719853188451023087019542019110756484.dcm | 3 + ....5781653050812637054888127655964957862.dcm | 3 + ....5812286521333908336594040927274738067.dcm | 3 + ....5880874384526854499475562779275913966.dcm | 3 + ...3.593843373509326635866104994225044111.dcm | 3 + ...3.613264099494174407778943900285243255.dcm | 3 + ....6143004276963640907894318138477941197.dcm | 3 + ....6168033529901995753477563663928741494.dcm | 3 + ....6169888496205029341086828801262283710.dcm | 3 + ....6254149428289287983397667937025799347.dcm | 3 + ...3.633777431005242128814320303450183171.dcm | 3 + ...3.633912715182934771967647933151861974.dcm | 3 + ....6357384537211774088820520996004232717.dcm | 3 + ....6441376112498392457505254077668665834.dcm | 3 + ....6597065663458681246337483004965329921.dcm | 3 + ....6658573022075014665744812907032604318.dcm | 3 + ....6712840246707125512465521980995994214.dcm | 3 + ....6761223945478082667680320657623117205.dcm | 3 + ....6835245296550316883645515092656002537.dcm | 3 + ...3.685137084942333907959889524188759462.dcm | 3 + ...43.69263144930629956310427638087391346.dcm | 3 + ....7027794401230589409829239012624630832.dcm | 3 + ....7081301029476025717966865993770847007.dcm | 3 + ....7206583323913954541251581408021240329.dcm | 3 + ....7318143735353060334061283529355555291.dcm | 3 + ...3.742206910498381871182263929677311012.dcm | 3 + ....7422432385979430023558602012600868853.dcm | 3 + ....7460373818674061099498239168987508407.dcm | 3 + ....7481349549399418277409074537978904253.dcm | 3 + ....7487959755683702293138632326735878290.dcm | 3 + ...3.750956438087847654699077740357585383.dcm | 3 + ....7631610506895401710001102852770929658.dcm | 3 + ....7732652048134199842429252293022176383.dcm | 3 + ....7781972752181582862799072108849713022.dcm | 3 + ....7782577618735046667439126660312710220.dcm | 3 + ....7870401717837385777336241245171044415.dcm | 3 + ....7980660904868944201449075257213949689.dcm | 3 + ....7981154001954224966931165302206197255.dcm | 3 + ....8049163574043799507014417847828975199.dcm | 3 + ....8162611483932317619983452423520154636.dcm | 3 + ....8168150317348920894532963717230388834.dcm | 3 + ....8171430137379649933713011214015618074.dcm | 3 + ....8179892071462079531760347170645287392.dcm | 3 + ....8237365378403393195438478012205382174.dcm | 3 + ....8273438353748895042768359085531460113.dcm | 3 + ...3.829681415896452665224477136101765237.dcm | 3 + ....8407381349982180990318229737569507269.dcm | 3 + ....8464855210532784225543523074112356205.dcm | 3 + ....8525134786162135368787331294760377714.dcm | 3 + ....8574549265078372915904470948065785868.dcm | 3 + ....8575585509648595543554072977940251480.dcm | 3 + ....8621432243665445149839266975414641854.dcm | 3 + ....8720810616903982173793875470576819469.dcm | 3 + ....8887380356061938605931075302276702327.dcm | 3 + ....8940172457999261511869688730520958178.dcm | 3 + ....8975838799991772102294406794136089481.dcm | 3 + ....9013674312261915713054516037041527442.dcm | 3 + ...3.911407342250437851605538971516132393.dcm | 3 + ....9312885525839692115388851990352305547.dcm | 3 + ....9586854040391990792601446166314705903.dcm | 3 + ....9596694057118048084660713935839816582.dcm | 3 + ....9615381644600228736239254996226957517.dcm | 3 + ....9699019752073144820794159416056179223.dcm | 3 + ....9701334368700607879183255977802156769.dcm | 3 + ...3.989578340813719233003086565949201853.dcm | 3 + ....9927869533253832950062842559108355546.dcm | 3 + Images/LargeSeriesWithContour/rtstruct.dcm | 3 + Images/MR/1.dcm | 3 + LICENSE | 21 + README.md | 1289 + SECURITY.md | 41 + SUPPORT.md | 25 + Source/Anonymizer/.editorconfig | 207 + Source/Anonymizer/CodeCoverage.runsettings | 24 + .../AnonymisationTagHandler.cs | 43 + .../AnonymizerEngineTests.cs | 443 + .../ConfidentialityProfileTests.cs | 168 + .../DICOMAnonymizer.Tests.csproj | 44 + .../DICOMAnonymizer.Tests/SpecTests.cs | 233 + .../DICOMAnonymizer.Tests/SpecXML/part04.xml | 47684 ++++++++++++++++ .../DICOMAnonymizer.Tests/SpecXML/part15.xml | 24293 ++++++++ .../TestData/CT-MONO2-16-ankle | Bin 0 -> 525436 bytes .../DICOMAnonymizer.Tests/TestData/CT1_J2KI | Bin 0 -> 14156 bytes .../DICOMAnonymizer/AnonymizeEngine.cs | 499 + .../DICOMAnonymizer/ConfidentialityProfile.cs | 678 + .../DICOMAnonymizer/DICOMAnonymizer.csproj | 25 + .../Anonymizer/DICOMAnonymizer/ITagHandler.cs | 42 + .../Anonymizer/DICOMAnonymizer/TagOrIndex.cs | 32 + .../DICOMAnonymizer/Tools/SOPClassFinder.cs | 425 + Source/Microsoft.Gateway/.editorconfig | 267 + .../Microsoft.Gateway/Microsoft.Gateway.sln | 190 + .../ChannelData.cs | 45 + .../Gateway/AETConfig.cs | 126 + .../Gateway/AETConfigType.cs | 23 + .../Gateway/AnonymisationMethod.cs | 24 + .../Gateway/ClientAETConfig.cs | 98 + .../Gateway/DicomEndPoint.cs | 92 + .../Gateway/DicomTagAnonymisation.cs | 58 + .../Gateway/ModelChannelConstraints.cs | 118 + .../Gateway/ModelConstraintsConfig.cs | 94 + .../Gateway/TagReplacement.cs | 94 + .../Gateway/TagReplacementOperation.cs | 18 + .../HashingFunctions.cs | 36 + .../Helpers/DicomCompressionHelpers.cs | 306 + ...erEye.Azure.Segmentation.API.Common.csproj | 33 + .../ModelResult.cs | 78 + .../AnonymisationTagHandler.cs | 360 + .../ConstraintResult.cs | 112 + .../IInnerEyeSegmentationClient.cs | 72 + .../InnerEyeSegmentationClient.cs | 458 + ....InnerEye.Azure.Segmentation.Client.csproj | 31 + .../RetryHandler.cs | 78 + .../SegmentationModel.cs | 49 + .../TagReplacer.cs | 228 + .../DicomExtensions.cs | 29 + .../Implementations/DicomDataSender.cs | 233 + .../Implementations/ListenerDataReceiver.cs | 170 + .../Implementations/ListenerDicomSaver.cs | 225 + .../Implementations/ListenerDicomService.cs | 275 + .../Interfaces/IDicomDataReceiver.cs | 47 + .../Interfaces/IDicomDataSender.cs | 52 + .../Interfaces/IDicomSaver.cs | 47 + ...soft.InnerEye.Listener.DataProvider.csproj | 25 + .../ApplicationEntityValidationHelpers.cs | 76 + .../DicomDataReceiverProgressEventArgs.cs | 92 + .../Models/DicomFileStoreParameters.cs | 56 + .../Models/DicomOperationResult.cs | 23 + .../Models/DicomReceiveProgressCodes.cs | 26 + .../Models/DicomStoreException.cs | 77 + .../GroupTests.cs | 38 + ...oft.InnerEye.DicomConstraints.Tests.csproj | 33 + .../OrderConstraintTests.cs | 161 + .../SerializationTests.cs | 153 + .../TagRequirements.cs | 41 + .../currenttypes.json | 250 + .../Constraints/DicomConstraint.cs | 32 + .../Constraints/DicomConstraintResult.cs | 58 + .../Constraints/DicomOrderedTag.cs | 87 + .../Constraints/DicomTagConstraint.cs | 34 + .../Constraints/DicomTagIndex.cs | 93 + .../Constraints/GroupConstraint.cs | 197 + .../Constraints/Order.cs | 48 + .../Constraints/OrderedDateTimeConstraint.cs | 90 + .../Constraints/OrderedDoubleConstraint.cs | 90 + .../Constraints/OrderedIntConstraint.cs | 90 + .../Constraints/OrderedString.cs | 146 + .../Constraints/OrderedStringConstraint.cs | 90 + .../Constraints/OrderedUIDStringSelector.cs | 92 + .../Constraints/RegexConstraint.cs | 122 + .../Constraints/RequiredTagConstraint.cs | 154 + .../Constraints/StringContainsConstraint.cs | 103 + .../Constraints/TimeOrderConstraint.cs | 88 + .../Implementation/BaseOrderConstraint.cs | 54 + .../Implementation/IInitializable.cs | 15 + .../Implementation/ISelector.cs | 17 + .../Implementation/OrderingConstraint.cs | 63 + .../Implementation/Selectors.cs | 83 + ...Microsoft.InnerEye.DicomConstraints.csproj | 30 + .../AssociationStatus.cs | 183 + .../LogEntry.cs | 389 + .../LogEntryType.cs | 34 + .../MessageQueueStatus.cs | 73 + .../Microsoft.InnerEye.Gateway.Logging.csproj | 30 + .../ServiceStatus.cs | 74 + .../MessageQueuePermissionsException.cs | 48 + .../Exceptions/MessageQueueReadException.cs | 48 + .../MessageQueueTransactionBeginException.cs | 48 + .../Exceptions/MessageQueueWriteException.cs | 48 + .../GatewayMessageQueue.cs | 37 + .../IMessageQueue.cs | 44 + .../IQueueTransaction.cs | 26 + ...ft.InnerEye.Gateway.MessageQueueing.csproj | 25 + .../Sqlite/SqliteMessageQueue.cs | 320 + .../Sqlite/SqliteMessageQueueTransaction.cs | 357 + .../AssociationQueueItemBase.cs | 54 + .../ConfigurationServiceConfig.cs | 97 + .../DeleteQueueItem.cs | 97 + .../DequeueServiceConfig.cs | 121 + .../DicomFileInformation.cs | 151 + .../DownloadQueueItem.cs | 171 + .../DownloadServiceConfig.cs | 97 + .../GatewayApplicationEntity.cs | 42 + .../GatewayProcessorConfig.cs | 99 + .../GatewayReceiveConfig.cs | 79 + .../Microsoft.InnerEye.Gateway.Models.csproj | 31 + .../ProcessorSettings.cs | 69 + .../PushQueueItem.cs | 101 + .../QueueItemBase.cs | 51 + .../ReceiveServiceConfig.cs | 140 + .../ServiceSettings.cs | 60 + .../UploadQueueItem.cs | 103 + .../Exceptions/SqliteReadException.cs | 48 + .../Exceptions/SqliteWriteException.cs | 48 + .../Extensions/SqliteExtensions.cs | 127 + .../Microsoft.InnerEye.Gateway.Sqlite.csproj | 27 + .../SqliteManager.cs | 102 + .../AETConfigModel.cs | 93 + .../DicomExtensions.cs | 89 + .../DryRunFolders.cs | 46 + .../Microsoft.InnerEye.Listener.Common.csproj | 37 + .../Providers/AETConfigProvider.cs | 97 + .../Providers/ApplyAETModelConfigProvider.cs | 265 + .../Providers/BaseConfigProvider.cs | 107 + .../GatewayProcessorConfigProvider.cs | 89 + .../Providers/GatewayReceiveConfigProvider.cs | 60 + .../Services/ConfigurationService.cs | 203 + .../Services/DequeueClientServiceBase.cs | 301 + .../Services/IService.cs | 25 + .../Services/ServiceHelpers.cs | 49 + .../Services/ServiceNames.cs | 18 + .../Services/ServiceWrapper.cs | 98 + .../Services/ThreadedServiceBase.cs | 593 + ...crosoft.InnerEye.Listener.Processor.csproj | 47 + .../Program.cs | 82 + .../ProjectInstaller.Designer.cs | 60 + .../ProjectInstaller.cs | 65 + .../ProjectInstaller.resx | 129 + .../Services/DeleteService.cs | 121 + .../Services/DownloadService.cs | 309 + .../Services/PushService.cs | 251 + .../Services/UploadService.cs | 475 + .../log4net.config | 24 + ...icrosoft.InnerEye.Listener.Receiver.csproj | 46 + .../Program.cs | 51 + .../ProjectInstaller.Designer.cs | 60 + .../ProjectInstaller.cs | 65 + .../ProjectInstaller.resx | 129 + .../Services/ReceiveService.cs | 296 + .../log4net.config | 24 + .../Assets/SCP.cfg | 78 + .../Assets/SCU.cfg | 198 + .../Documentation/TestResult.cs | 121 + .../Documentation/VerificationDocument.cs | 95 + .../Helpers/DcmtkHelpers.cs | 133 + .../Helpers/MSMQHelpers.cs | 38 + ...soft.InnerEye.Listener.Tests.Common.csproj | 166 + .../BaseTestClass.cs | 692 + .../ConfigurationProviderTests.cs | 807 + .../DataProviderTests/DebugTextWriter.cs | 44 + .../DicomDataReceiverTests.cs | 150 + .../DataProviderTests/DicomDataSenderTests.cs | 114 + .../ExtensionTests/DicomFileExtensionTests.cs | 105 + .../LoggingTests/GatewayLoggingTests.cs | 19 + .../MessageQueueTests/QueueItemQueueTests.cs | 195 + .../SQLiteMessageQueueTests.cs | 678 + .../Microsoft.InnerEye.Listener.Tests.csproj | 61 + .../Models/MockAETConfigProvider.cs | 24 + .../Models/MockConfigurationProvider.cs | 43 + .../Models/MockInnerEyeSegmentationClient.cs | 149 + .../ServiceTests/ConfigurationServiceTests.cs | 110 + .../ServiceTests/DicomAnonymisationTests.cs | 370 + .../ServiceTests/DownloadServiceTests.cs | 398 + .../ServiceTests/PushServiceTests.cs | 196 + .../ServiceTests/ReceiveServiceTests.cs | 275 + .../ServiceTests/SystemTests.cs | 251 + .../ServiceTests/UploadServiceTests.cs | 246 + .../GatewayModelRulesConfig.json | 102 + .../GatewayProcessorConfig.json | 22 + .../GatewayReceiveConfig.json | 24 + .../Assets/Icon.ico | Bin 0 -> 367863 bytes .../CustomAction.config | 32 + .../CustomActions.cs | 142 + .../LicenseKeyForm.Designer.cs | 195 + .../LicenseKeyForm.cs | 93 + .../LicenseKeyForm.resx | 6257 ++ ...osoft.InnerEye.Listener.Wix.Actions.csproj | 188 + .../Properties/AssemblyInfo.cs | 34 + .../Properties/Resources.Designer.cs | 63 + .../Properties/Resources.resx | 120 + .../app.config | 59 + .../packages.config | 25 + .../Microsoft.InnerEye.Listener.Wix/Icon.ico | Bin 0 -> 367863 bytes .../Library.wxs | 105 + .../Microsoft.InnerEye.Listener.Wix.wixproj | 91 + .../Microsoft.InnerEye.Listener.Wix/Readme.md | 13 + .../WixUI_FeatureTree2.wxs | 42 + .../filter.xsl | 20 + Source/Microsoft.Gateway/NuGet.config | 12 + .../GatewayModelRulesConfigPassThrough1.json | 81 + .../GatewayModelRulesConfigPassThrough2.json | 40 + .../GatewayModelRulesConfigPelvis.json | 102 + .../GatewayProcessorConfig.json | 22 + .../GatewayReceiveConfig.json | 24 + .../Scripts/ProcessorServiceInstaller.ps1 | 9 + .../Scripts/ReceiveServiceInstaller.ps1 | 9 + Source/Microsoft.Gateway/download_dcmtk.ps1 | 6 + THIRDPARTYNOTICES.md | 3956 ++ pull_request_template.md | 7 + 411 files changed, 110662 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/build-test.yml create mode 100644 .gitignore create mode 100644 Build/azure-pipelines.yml create mode 100644 CODE_OF_CONDUCT.md create mode 100644 Docs/Additional Documents/DeployResourcesAzureStackHub.md create mode 100644 Docs/Additional Documents/EndToEndDemo.md create mode 100644 Docs/Additional Documents/EndToEndOnAzureStackHub.md create mode 100644 Docs/Additional Documents/InferencingAPIs.md create mode 100644 Docs/Additional Documents/InferencingContainer.md create mode 100644 Docs/Additional Documents/InferencingEngine.md create mode 100644 Docs/Additional Documents/MoreAboutInnerEyeProject.md create mode 100644 Docs/Additional Documents/Setup.md create mode 100644 Docs/Additional Documents/VideoAndBlogs.md create mode 100644 Docs/Diagram.md create mode 100644 Docs/Help and Bugs Reporting/BugReporting.md create mode 100644 Docs/Help and Bugs Reporting/PullRequestsGuidelines.md create mode 100644 Docs/Help and Bugs Reporting/Stack-overflowAndOtherChannels.md create mode 100644 Docs/environment.png create mode 100644 Docs/sequence.png create mode 100644 Docs/sequence.wsd create mode 100644 GeoPol.xml create mode 100644 Images/1ValidSmall/1.dcm create mode 100644 Images/1ValidSmall/10.dcm create mode 100644 Images/1ValidSmall/11.dcm create mode 100644 Images/1ValidSmall/12.dcm create mode 100644 Images/1ValidSmall/13.dcm create mode 100644 Images/1ValidSmall/14.dcm create mode 100644 Images/1ValidSmall/15.dcm create mode 100644 Images/1ValidSmall/16.dcm create mode 100644 Images/1ValidSmall/17.dcm create mode 100644 Images/1ValidSmall/18.dcm create mode 100644 Images/1ValidSmall/19.dcm create mode 100644 Images/1ValidSmall/2.dcm create mode 100644 Images/1ValidSmall/20.dcm create mode 100644 Images/1ValidSmall/3.dcm create mode 100644 Images/1ValidSmall/4.dcm create mode 100644 Images/1ValidSmall/5.dcm create mode 100644 Images/1ValidSmall/6.dcm create mode 100644 Images/1ValidSmall/7.dcm create mode 100644 Images/1ValidSmall/8.dcm create mode 100644 Images/1ValidSmall/9.dcm create mode 100644 Images/InvalidPN/1.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1090460865966542526486876149643126065.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1113583911936106161255831110435725437.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1247728137096394998404446793947041469.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1268297749850909254845172237211472094.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276409595806088158265421354038204673.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276823266322239590774784606318904757.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1309493738860257695938496641148832585.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1315228061378753091775225585475513904.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1424821720661730651575121131022380064.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.149540744391437319866380830011665495.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1504292588464778016131047913246200712.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1586289423697072709053282986003052130.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603507555357901003873369762405395158.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603941769404672370986212481856666706.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1733723835157503707471772250948338056.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.177159971429155354323352122369910018.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2065039152027214948185192787016609102.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2077707482707913294090849361486537575.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2091056545198481824972740777538855628.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2300191907444461623117367828109639362.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2311513203284923046703814127142600862.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.234848398285035287627993400411403947.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2366245601673390599661173419249430152.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2517875113743844279077466488297548492.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2528918310531862024240590111330526032.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.255472593933636444021718369425699674.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.272404100495827097094503485633903342.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2856276318351938990833279631467510060.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.28621440201171685228804903937000813.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2929087666691054004992797698806946100.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2965520600218423570825909564893574418.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3039200638329081880003952315701223755.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3138565465547482227814490173133974101.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3264163721230795368390441633568856169.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3329214252684256799918043573009559701.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3342450797385800689172373508778910683.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3348092881200381409012471499478647143.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3456195802144665971608371497453372482.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3531577771862774762674919299088992667.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3559519125404573210574469451974196707.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3707432864349768500524190555289963771.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3826387874733181325715780105257912381.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3884266638195393445767846913412404340.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3901472286358631291254773359964022615.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077627525916031544392875981537574880.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077765416632201049866028033810645876.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.416267245062537504426664884554455695.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4220429893402217707626563486809748036.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4227760063788177814850539832569856657.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4361110441258480612761531589119880729.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4429117347680799243274407179054009519.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4430789403887947931838641862616347273.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4545923077608497415467351747154540694.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.457099872952531787279666134580381486.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4583195912092827698588179857276488969.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4593469450985571044589247082565455469.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4661186853222973408651088617727253235.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4678179273222049229856606186006082767.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4714785946536100308562944102363288971.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4766066604650145714566023185265207574.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4786946907986646372976065107744927432.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.479594740413779986935166935711464664.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4820400408130735437856746766113162355.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4847684476803386246306224608512829862.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4848358862206565187800419652953160929.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4867098704721969152550959342284226672.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4974603962485423328908158546094390835.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5032568314730441475611031612232702813.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5117600506941670944922941183791284804.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.514204938254240720422969359500861281.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5168782564514843969477858686535623503.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5214260960761315974879308927152578120.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5353352661842228977009124604457342114.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5375120196677187954364257785394062354.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5480675569475985780063595571287169751.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5508569889704436607767594729119990566.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5521222100884173015423683049999264236.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.560743097336335340588260575577289706.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5639810407786340123860181536716326619.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5692514868855490639520084349215228577.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5719853188451023087019542019110756484.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5781653050812637054888127655964957862.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5812286521333908336594040927274738067.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5880874384526854499475562779275913966.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.593843373509326635866104994225044111.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.613264099494174407778943900285243255.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6143004276963640907894318138477941197.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6168033529901995753477563663928741494.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6169888496205029341086828801262283710.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6254149428289287983397667937025799347.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633777431005242128814320303450183171.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633912715182934771967647933151861974.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6357384537211774088820520996004232717.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6441376112498392457505254077668665834.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6597065663458681246337483004965329921.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6658573022075014665744812907032604318.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6712840246707125512465521980995994214.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6761223945478082667680320657623117205.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6835245296550316883645515092656002537.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.685137084942333907959889524188759462.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.69263144930629956310427638087391346.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7027794401230589409829239012624630832.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7081301029476025717966865993770847007.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7206583323913954541251581408021240329.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7318143735353060334061283529355555291.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.742206910498381871182263929677311012.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7422432385979430023558602012600868853.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7460373818674061099498239168987508407.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7481349549399418277409074537978904253.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7487959755683702293138632326735878290.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.750956438087847654699077740357585383.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7631610506895401710001102852770929658.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7732652048134199842429252293022176383.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7781972752181582862799072108849713022.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7782577618735046667439126660312710220.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7870401717837385777336241245171044415.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7980660904868944201449075257213949689.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7981154001954224966931165302206197255.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8049163574043799507014417847828975199.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8162611483932317619983452423520154636.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8168150317348920894532963717230388834.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8171430137379649933713011214015618074.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8179892071462079531760347170645287392.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8237365378403393195438478012205382174.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8273438353748895042768359085531460113.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.829681415896452665224477136101765237.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8407381349982180990318229737569507269.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8464855210532784225543523074112356205.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8525134786162135368787331294760377714.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8574549265078372915904470948065785868.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8575585509648595543554072977940251480.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8621432243665445149839266975414641854.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8720810616903982173793875470576819469.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8887380356061938605931075302276702327.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8940172457999261511869688730520958178.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8975838799991772102294406794136089481.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9013674312261915713054516037041527442.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.911407342250437851605538971516132393.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9312885525839692115388851990352305547.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9586854040391990792601446166314705903.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9596694057118048084660713935839816582.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9615381644600228736239254996226957517.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9699019752073144820794159416056179223.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9701334368700607879183255977802156769.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.989578340813719233003086565949201853.dcm create mode 100644 Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9927869533253832950062842559108355546.dcm create mode 100644 Images/LargeSeriesWithContour/rtstruct.dcm create mode 100644 Images/MR/1.dcm create mode 100644 LICENSE create mode 100644 README.md create mode 100644 SECURITY.md create mode 100644 SUPPORT.md create mode 100644 Source/Anonymizer/.editorconfig create mode 100644 Source/Anonymizer/CodeCoverage.runsettings create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/AnonymisationTagHandler.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/AnonymizerEngineTests.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/ConfidentialityProfileTests.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/DICOMAnonymizer.Tests.csproj create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/SpecTests.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part04.xml create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part15.xml create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/TestData/CT-MONO2-16-ankle create mode 100644 Source/Anonymizer/DICOMAnonymizer.Tests/TestData/CT1_J2KI create mode 100644 Source/Anonymizer/DICOMAnonymizer/AnonymizeEngine.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer/ConfidentialityProfile.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer/DICOMAnonymizer.csproj create mode 100644 Source/Anonymizer/DICOMAnonymizer/ITagHandler.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer/TagOrIndex.cs create mode 100644 Source/Anonymizer/DICOMAnonymizer/Tools/SOPClassFinder.cs create mode 100644 Source/Microsoft.Gateway/.editorconfig create mode 100644 Source/Microsoft.Gateway/Microsoft.Gateway.sln create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ChannelData.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfigType.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AnonymisationMethod.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ClientAETConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomEndPoint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomTagAnonymisation.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelChannelConstraints.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelConstraintsConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacement.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacementOperation.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/HashingFunctions.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Helpers/DicomCompressionHelpers.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Microsoft.InnerEye.Azure.Segmentation.API.Common.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ModelResult.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/AnonymisationTagHandler.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/ConstraintResult.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/IInnerEyeSegmentationClient.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/InnerEyeSegmentationClient.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/Microsoft.InnerEye.Azure.Segmentation.Client.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/RetryHandler.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/SegmentationModel.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/TagReplacer.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/DicomExtensions.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/DicomDataSender.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDataReceiver.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomSaver.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataReceiver.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataSender.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomSaver.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Microsoft.InnerEye.Listener.DataProvider.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/ApplicationEntityValidationHelpers.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomDataReceiverProgressEventArgs.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomFileStoreParameters.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomOperationResult.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomReceiveProgressCodes.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomStoreException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/GroupTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/Microsoft.InnerEye.DicomConstraints.Tests.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/OrderConstraintTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/SerializationTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/TagRequirements.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/currenttypes.json create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraintResult.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomOrderedTag.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagIndex.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/GroupConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/Order.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDateTimeConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDoubleConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedIntConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedString.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedStringConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedUIDStringSelector.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RegexConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RequiredTagConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/StringContainsConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/TimeOrderConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/BaseOrderConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/IInitializable.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/ISelector.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/OrderingConstraint.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/Selectors.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Microsoft.InnerEye.DicomConstraints.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/AssociationStatus.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntry.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntryType.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/MessageQueueStatus.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/Microsoft.InnerEye.Gateway.Logging.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/ServiceStatus.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueuePermissionsException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueReadException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueTransactionBeginException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueWriteException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/GatewayMessageQueue.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IMessageQueue.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IQueueTransaction.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Microsoft.InnerEye.Gateway.MessageQueueing.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueue.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueueTransaction.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/AssociationQueueItemBase.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ConfigurationServiceConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DeleteQueueItem.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DequeueServiceConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DicomFileInformation.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadQueueItem.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadServiceConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayApplicationEntity.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayProcessorConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayReceiveConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/Microsoft.InnerEye.Gateway.Models.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ProcessorSettings.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/PushQueueItem.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/QueueItemBase.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ReceiveServiceConfig.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ServiceSettings.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/UploadQueueItem.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteReadException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteWriteException.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Extensions/SqliteExtensions.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Microsoft.InnerEye.Gateway.Sqlite.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/SqliteManager.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/AETConfigModel.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DicomExtensions.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DryRunFolders.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Microsoft.InnerEye.Listener.Common.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/AETConfigProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/ApplyAETModelConfigProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/BaseConfigProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayProcessorConfigProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayReceiveConfigProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ConfigurationService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/DequeueClientServiceBase.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/IService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceHelpers.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceNames.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceWrapper.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ThreadedServiceBase.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Microsoft.InnerEye.Listener.Processor.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Program.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.Designer.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.resx create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DeleteService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DownloadService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/PushService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/UploadService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/log4net.config create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Microsoft.InnerEye.Listener.Receiver.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Program.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.Designer.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.resx create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Services/ReceiveService.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/log4net.config create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCP.cfg create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCU.cfg create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/TestResult.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/VerificationDocument.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/DcmtkHelpers.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/MSMQHelpers.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Microsoft.InnerEye.Listener.Tests.Common.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/BaseTestClass.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ConfigurationProviderTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DebugTextWriter.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataReceiverTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataSenderTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ExtensionTests/DicomFileExtensionTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/LoggingTests/GatewayLoggingTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/QueueItemQueueTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/SQLiteMessageQueueTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Microsoft.InnerEye.Listener.Tests.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockAETConfigProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockConfigurationProvider.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockInnerEyeSegmentationClient.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ConfigurationServiceTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DicomAnonymisationTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DownloadServiceTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/PushServiceTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ReceiveServiceTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/SystemTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/UploadServiceTests.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfig.json create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayProcessorConfig.json create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayReceiveConfig.json create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Assets/Icon.ico create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomAction.config create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomActions.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.Designer.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.resx create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Microsoft.InnerEye.Listener.Wix.Actions.csproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/AssemblyInfo.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.Designer.cs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.resx create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/app.config create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/packages.config create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Icon.ico create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Library.wxs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Microsoft.InnerEye.Listener.Wix.wixproj create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Readme.md create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/WixUI_FeatureTree2.wxs create mode 100644 Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/filter.xsl create mode 100644 Source/Microsoft.Gateway/NuGet.config create mode 100644 Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough1.json create mode 100644 Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough2.json create mode 100644 Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPelvis.json create mode 100644 Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json create mode 100644 Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json create mode 100644 Source/Microsoft.Gateway/Scripts/ProcessorServiceInstaller.ps1 create mode 100644 Source/Microsoft.Gateway/Scripts/ReceiveServiceInstaller.ps1 create mode 100644 Source/Microsoft.Gateway/download_dcmtk.ps1 create mode 100644 THIRDPARTYNOTICES.md create mode 100644 pull_request_template.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3ef94c5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +*.dcm filter=lfs diff=lfs merge=lfs -text +*.nii.gz filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..93fcd5c --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,64 @@ +name: BuildAndTest.CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +env: + solution: './Source/Microsoft.Gateway/Microsoft.Gateway.sln' + buildPlatform: x64 + buildConfiguration: Release + +jobs: + build-test: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v2 + with: + lfs: true + + - name: Setup Nuget + uses: nuget/setup-nuget@v1 + with: + nuget-version: 'latest' + + - name: Setup MSBuild + uses: microsoft/setup-msbuild@v1.0.2 + + - name: Nuget Restore + run: nuget restore ${{ env.solution }} + + - name: Download Dependencies + shell: Powershell + run: | + cd .\Source\Microsoft.Gateway + .\download_dcmtk.ps1 + + + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: 'csharp' + + - name: Build Solution + run: msbuild.exe /nologo /t:build /p:UseSharedCompilation=false /p:Configuration=${{ env.buildConfiguration }} /p:Platform=${{ env.buildPlatform }} ${{ env.solution }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 + + - name: Setup VSTest Path + uses: darenm/Setup-VSTest@v1 + + - name: Test Solution + shell: Powershell + run: | + $erroractionpreference = "stop" + + $TestFiles = $(Get-ChildItem $PATH -include *tests*.dll -exclude "*TestAdapter*","*TestPlatform*","*MSTest*" -recurse | where {$_.FullName -notlike "*obj*"}).fullname + $TestArgs = $TestFiles + "/Platform:x64" + + vstest.console.exe $TestArgs + exit $lastexitcode \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..061edc3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,365 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ +*.fakes + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Typescript v1 declaration files +typings/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Telerik's JustMock configuration file +*.jmconfig + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Others +*.dir/ +*.sdf +*.opensdf +*.cache +*.opendb +*.ipch +*.exe +packages +*.db +.vs +*.messages +*.filters +*.psess +__pycache__ +*~ +ecf/ +/.vscode + +/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Service1.Generated.wxs +/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Service2.Generated.wxs +/Source/Microsoft.Gateway/dcmtk-3.6.5-win64-dynamic +*.zip +/Source/Microsoft.Gateway/dicom3tools diff --git a/Build/azure-pipelines.yml b/Build/azure-pipelines.yml new file mode 100644 index 0000000..ecab7da --- /dev/null +++ b/Build/azure-pipelines.yml @@ -0,0 +1,62 @@ +# .NET Desktop +# Build and run tests for .NET Desktop or Windows classic desktop solutions. +# Add steps that publish symbols, save build artifacts, and more: +# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net + +trigger: +- master + +pool: + vmImage: 'windows-latest' + +variables: + solution: '**/Source/Microsoft.Gateway/Microsoft.Gateway.sln' + buildPlatform: 'x64' + buildConfiguration: 'Release' + +steps: +- checkout: self + lfs: true +- task: NuGetToolInstaller@1 + + +- task: NuGetCommand@2 + inputs: + command: 'restore' + restoreSolution: '$(solution)' + feedsToUse: 'config' + nugetConfigPath: 'Source/Microsoft.Gateway/NuGet.config' + +- task: PowerShell@2 + inputs: + name: 'Download DCMTK' + filePath: '.\Source\Microsoft.Gateway\download_dcmtk.ps1' + workingDirectory: '.\Source\Microsoft.Gateway\' + failOnStderr: true + +- task: VSBuild@1 + inputs: + solution: '$(solution)' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: VSTest@2 + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: | + **\*test*.dll + !**\*TestAdapter.dll + !**\obj\** + searchFolder: '$(System.DefaultWorkingDirectory)/Source' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + +- task: CredScan@3 + +- task: ComponentGovernanceComponentDetection@0 + inputs: + scanType: 'Register' + verbosity: 'Verbose' + alertWarningLevel: 'High' + failOnAlert: true + \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..f9ba8cf --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/Docs/Additional Documents/DeployResourcesAzureStackHub.md b/Docs/Additional Documents/DeployResourcesAzureStackHub.md new file mode 100644 index 0000000..aade871 --- /dev/null +++ b/Docs/Additional Documents/DeployResourcesAzureStackHub.md @@ -0,0 +1,3 @@ +# Deploying resources on Azure Stack Hub + +@Edwin to complete \ No newline at end of file diff --git a/Docs/Additional Documents/EndToEndDemo.md b/Docs/Additional Documents/EndToEndDemo.md new file mode 100644 index 0000000..2cebddf --- /dev/null +++ b/Docs/Additional Documents/EndToEndDemo.md @@ -0,0 +1,38 @@ +# How to run end to end demo on local environment? + +Here are some quick steps to run end to end demo on your local environment. + +Do SSH into the GPU VM, first command is docker images to get the image id of the modified head and neck container. Then run it interactively using + +1. Start the GPU VM which has Inferencing container. Get the public IP and copy it. +2. Do SSH to this VM using - SSH :IP address +3. If prompted enter "yes" +4. Now it will ask for password. Enter the password: +5. After successful login it will open the VM shell. In the shell run below command. +6. docker run -it --entrypoint=/bin/bash -p 8086:5000 -e AZURE_STORAGE_ACCOUNT_NAME=name -e AZURE_STORAGE_KEY= -e AZURE_STORAGE_ENDPOINT= --gpus all +7. conda activate nnenv +8. python web-api.py +9. Clone https://msdsip@dev.azure.com/msdsip/AshInnerEye/_git/Gateway +10. Clone https://msdsip@dev.azure.com/msdsip/AshInnerEye/_git/AshInnerEye +11. Set platform to x64 and build the project +12. Generate self signed certificate using below command in PowerShell window. Make sure you run it as Administrator. + `New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "mysite.local" -FriendlyName "InnerEyeDryRun" -NotAfter (Get-Date).AddYears(10)` +13. Copy the thumbprint and replace "KeyVaultAuthCertThumbprint" key value of Inferencing API and Worker Project in config file. + a. Microsoft.InnerEye.Azure.Segmentation.API.Console + b. Microsoft.InnerEye.Azure.Segmentation.Worker.Console +14. Replace the other keys in same file. +15. Build both projects. +16. Now run both project Inferencing API and Engine exe. from bin directory + a. Microsoft.InnerEye.Azure.Segmentation.Worker.Console.exe + b. Microsoft.InnerEye.Azure.Segmentation.API.Console.exe +17. Next thing is to run gateway receiver and processer: + a. Microsoft.InnerEye.Listener.Processor.exe + b. Microsoft.InnerEye.Listener.Receiver.exe +18. Now you have to navigate to headandNeck images folder, ideally this should be in the code where you have cloned the repo: + a. *:\AshInnerEye\InnerEyeCloud\Source\Images\HeadAndNeck\image +19. Open above path in PowerShell window. +20. Run these command on PowerShell - `storescu 104 -v --scan-directories -aec RGPelvisCT -aet Scanner .` +21. Open a suitable path in PowerShell where you want to store result. +22. Run on powershell `storescp 1105 -v -aet PACS -od . --sort-on-study-uid st` +23. Wait for results. + diff --git a/Docs/Additional Documents/EndToEndOnAzureStackHub.md b/Docs/Additional Documents/EndToEndOnAzureStackHub.md new file mode 100644 index 0000000..50dd7c3 --- /dev/null +++ b/Docs/Additional Documents/EndToEndOnAzureStackHub.md @@ -0,0 +1,10 @@ +# How to run end to end demo on Azure Stack hub? + +Here are the steps: + +1. TBD - share image over DICOM +2. Then, open the image (test H&N volume is located in **) and ensure you can see it in the app +3. Select '...' button that is shown when hovering over image in the explorer panel, select "Export", choose destination, check "Send Series", make sure that "Send Structure Set" is unchecked, and click "Export". +4. Observe the console output from solution components to ensure that the process is underway. Once process is complete you should see a yellow "new image received" icon in the left sidebar of the app. +5. Go into the Incoming tab on the app and press "Open" next to the latest segmentation received. + diff --git a/Docs/Additional Documents/InferencingAPIs.md b/Docs/Additional Documents/InferencingAPIs.md new file mode 100644 index 0000000..19c5190 --- /dev/null +++ b/Docs/Additional Documents/InferencingAPIs.md @@ -0,0 +1,56 @@ +# Inferencing APIs + +## Gateway Dicom – Inferencing API + +Inferencing API is one the main component of the Inner Eye architecture. Currently we have set of API calls, which are grouped into several functional groups and its part of InnerEye Cloud (classic cloud service) application. +(As part of architecture, Inferencing API is highlighted as below) + +![ash_architecture.png](https://dev.azure.com/msdsip/8520c5e0-ef36-49bc-983d-12972ea056e0/_apis/git/repositories/cecb2ded-12e0-46f2-a2fe-7bf99a94811f/Items?path=%2F.attachments%2Fash_architecture-461fa2d7-8655-4ce9-b5b9-e6572b51030f.png&download=false&resolveLfs=true&%24format=octetStream&api-version=5.0-preview.1&sanitize=true&versionDescriptor.version=wikiMaster) + +Below is the distribution of set of API call into as per their functional groups. Out of which we are working on Point 4 Inferencing API also test Point 5 for health check. + +**1. DICOM Configuration** + +These APIs configure DICOM endpoints that the Gateway can work with as well as routing rules for data that comes from these endpoints. +These APIs configure DICOM endpoints that the Gateway can work with as well as routing rules for data that comes from these endpoints. +**OSS implementation:** the configuration will be done via JSON files. These APIs are scrapped. + +*/api/v1/config/gateway/update* - Gets the expected version the Gateway should be updated to. +*/api/v1/config/gateway/receive* - Gets the current gateway "receive" configuration. +*/api/v1/config/gateway/processor* - Gets the current gateway "processor" configuration. +*/api/v1/config/gateway/destination/{callingAET}* - Gets the destination DicomEndPoint to send results to given the AET of the caller +*/api/v1/config/gateway/destination/{callingAET}/{calledAET}* - Gets the destination DicomEndPoint to send results to given the AET of the caller and the calling AET (the way our gateway is being called) +*/api/v1/config/aetconfig/{calledAET}/{callingAET}* - Download a collection of DICOM constraints based on called AET and calling AET + +**2. Data Upload** + +This API endpoint provides a way to upload data for persisting the images for subsequent machine learning. +**OSS implementation:** These API need to be updated to conform with DICOMWeb implementation. +*/api/v1/storage* - Upload DICOM series to long term storage. In V1 this API call needs to be replaced with a call to a DICOM Web STOW-RS + +**3. Feedback** + +These APIs facilitate a workflow where a corrected segmentation is sent back for further analysis. This is not used in V1; the APIs below should be removed. +OSS implementation: These API need to be removed +*/api/v1/feedback* - Upload a collection of DICOM files (segmentation masks). +*/ping* - check if API is still up. Keep for V1 +*/api/ping* - check if API is still up, with authentication. Remove for V1 + +**4. Inferencing** + +These APIs have to do with inferencing: +• Get the list of registered models +• Send image for inferencing +• Get progress +• Retrieve result + +**OSS implementation:** Most of these APIs remain and are essential to V1 operation +**/api/v1/models** - Returns a list of all models from Azure model blob container. This call is not needed for V1 implementation. This part was under discussion and based on meetings and discussion, for demos we are going to used two static model configurations. +**/api/v1/models/{modelId}/segmentation/{segmentationId}** - Checks the segmentation status for a given segmentation of a given model. +**/api/v1/models/{modelId}/segmentation/{segmentationId}/result** - Gets the result for a completed segmentation. +**/api/v1/models/{modelId}/segmentation** - Starts a segmentation. The content of the request should be a compressed zip file with a list of DICOM files with a folder per ChannelId E.g. ct\1.dcm, flair\1.dcm + +**5. Health check** + +*/ping* - check if API is still up. Keep for V1 +*/api/ping* - check if API is still up, with authentication. Remove for V1 \ No newline at end of file diff --git a/Docs/Additional Documents/InferencingContainer.md b/Docs/Additional Documents/InferencingContainer.md new file mode 100644 index 0000000..3365082 --- /dev/null +++ b/Docs/Additional Documents/InferencingContainer.md @@ -0,0 +1,3 @@ +# Inferencing Container + +@Mark, @Edwin - Help out \ No newline at end of file diff --git a/Docs/Additional Documents/InferencingEngine.md b/Docs/Additional Documents/InferencingEngine.md new file mode 100644 index 0000000..bead1c0 --- /dev/null +++ b/Docs/Additional Documents/InferencingEngine.md @@ -0,0 +1,7 @@ +# Inferencing Engine + +**What does Inferencing do and the flow related to the architecture?** + +Inferencing Engine works to transform the Nifti images to Dicom RT files. Inferencing Engine reads the Dicom Image from the Queue which is present in the byte form. The image is transformed into nifty image and pushes it to the Inferencing container where it send back the Nifti seg mask image. The Segmentation processor take the nifti images and transforms to Dicom RT file. These Dicom RT images are pushed to blob and the progress of this task is saved in Table storage. + +@Mark - To help out \ No newline at end of file diff --git a/Docs/Additional Documents/MoreAboutInnerEyeProject.md b/Docs/Additional Documents/MoreAboutInnerEyeProject.md new file mode 100644 index 0000000..8732885 --- /dev/null +++ b/Docs/Additional Documents/MoreAboutInnerEyeProject.md @@ -0,0 +1,3 @@ +# More about InnerEye project + +@Michela \ No newline at end of file diff --git a/Docs/Additional Documents/Setup.md b/Docs/Additional Documents/Setup.md new file mode 100644 index 0000000..8d7ccae --- /dev/null +++ b/Docs/Additional Documents/Setup.md @@ -0,0 +1,328 @@ +# Getting started +Here is a page with intros to DICOM and subject domain: https://dev.azure.com/msdsip/AshInnerEye/_wiki/wikis/AshInnerEye.wiki/24/Start-Here + +# Environment + +**Stack Hub environment portal**: https://portal.ppe2.stackpoc.com/#@avanadestackpoc.onmicrosoft.com +Resource group used for demo: **rgcnabgroup** + +# Chapter 1: Resource deployment +When it comes to resources deployment on Azure Environment we mostly think about using ARM templates, which is the good thing which stack hub also supports. We can deploy our resources using ARM template. In this setup we are taking help of [CNAB](https://cnab.io/) to bundle our infrastructure and deploy it on Azure Stack Hub. + +# Prerequisites + +- Azure Stack Hub subscription + +- Docker (Here is a link if you need to install [Docker Installation Instructions](https://docs.docker.com/get-docker/) ) + +- Porter (Here is a link if you need to install: Porter Installation Instructions [[Porter Installation Instructions]](https://porter.sh/install/)) + + > **NOTE:** be sure to add porter to your PATH + +- Service Principal that has been granted contributor access to your Azure Stack Hub subscription + + - You will need the following information for the service principal + - Client ID + - Client secret + - Object ID (this is different than the object id and can be found on the enterprise application area of your Azure Active Directory) + - Tenant ID + +- Your user account needs to have owner access to the subscription. (This is needed to assign access to the service principal for deployment) + +# Step 1: Prepare for Installation + +### Create CNAB Parameter File + +Locate the file named `azure-stack-profile.template.txt` and open it for editing. You will need to provide some values so the CNAB package can register your Azure Stack environment and deploy into it. Save the file as `azure-stack-profile.txt` after you have assigned the required values. + +``` +azure_stack_tenant_arm="Your Azure Stack Tenant Endpoint" +azure_stack_storage_suffix="Your Azure Stack Storage Suffix" +azure_stack_keyvault_suffix="Your Azure Stack KeyVault Suffix" +azure_stack_location="Your Azure Stack’s location identifier here." +azure_stack_resource_group="Your desired Azure Stack resource group name to create" +slicer_ip="IP address for your clinical endpoint for receiving DICOM RT" +``` + +### Generate Credentials + +Open a new shell window and make sure you are in the root directory of this repo. Run the command below to generate credentials required for deployment. Follow the prompts to assign values for the credentials needed. Select "specific value" from the interactive menu for each of the required credential fields. A description of each credential is provided below. + +``` +porter generate credentials +``` + +| Item | Description | +| :-------------------------- | :----------------------------------------------------------- | +| AZURE_STACK_SP_CLIENT_ID | The client id for the service principal that is registered with your Azure Stack Hub Subscription | +| AZURE_STACK_SP_PASSWORD | The secret associated with the service principal that is registered with your Azure Stack Hub Subscription | +| AZURE_STACK_SP_TENANT_DNS | The dns for the Azure Active Directory that is tied to your Azure Stack Hub (e.g. [mycomany.onmicrosoft.com](http://mycomany.onmicrosoft.com/) ) | +| AZURE_STACK_SUBSCRIPTION_ID | The subscription id for the subscription on your Azure Stack Hub that you want to deploy into | +| VM_PASSWORD | The password you would like to use for the login to the VM that is deployed as part of this CNAB package | + +# Step 2: Build CNAB + +Run the command below to build the Porter CNAB package. + +``` +porter build +``` + +# Step 3: Install CNAB + +### Install CNAB Package + +Run the below command to install the CNAB package. This will create a new resource group on you Azure Stack subscription and will deploy the solution into it. + +``` +porter install InnerEye --cred InnerEye --param-file "azure-stack-profile.txt" +``` + +### (Optional) Uninstall CNAB Package + +If you wish to remove the solution from your Azure Stack Hub, run the below command. Please note that this will delete the entire resource group that the solution was deployed into. If you have created any other custom resources in this resource group, they will also be deleted. + +``` +porter uninstall InnerEye --cred InnerEye --param-file "azure-stack-profile.txt" +``` + +# Step 4: Start Inferencing Container(s) + +- Get the IP of the Inferencing Container VM from the Azure Stack Hub Portal +- Connect to the VM via ssh +- Navigate to the app directory +- Make any necessary modifications to the model_inference_config.json file +- Start the containers by running the below commands + +``` +python setup-inferencing.py model_inference_config.json +``` + +# Step 5: Start the Gateway + +- Get the IP of the Inferencing Container VM from the Azure Stack Hub Portal +- Connect to the VM via Remote Desktop Protocol (RDP) +- Open the gateway.msi file on the desktop + +## Summary of Deployment Components + +- KeyVault and grants read access to Service Principal +- Storage Account +- GPU Linux VM to host inferencing containers +- App service plan to host Inferencing API and Inferencing Engine +- Inferencing API app service +- Inferencing Engine app service +- Gateway VM + +# Chapter 2: Building and deploying the code +We can always use Azure DevOps CICD pipelines to build and deploy the code on Infrastructure created. In this chapter we will talk about how to build and deploy code using local environment. + +# Prerequisites + +- Cloned git repositories. +- Visual Studio 2017 +- Azure Stack Hub Storage Account Details +- Gateway VM Details +- Inferencing container Details + +### Clone the repos + +To clone the code to local environment please follow below steps: + +1. First you need to clone the InnerEye Cloud Solution. + +``` +git clone https://msdsip@dev.azure.com/msdsip/AshInnerEye/_git/AshInnerEye +``` + +2. After cloning the InnerEye Cloud Solution, second repository to clone is Gateway. + +``` +git clone https://msdsip@dev.azure.com/msdsip/AshInnerEye/_git/Gateway +``` + +- > **NOTE:** Make sure you clone both the repos at root folder of any directory, this is to avoid max-length path issue. + +### Building the solutions + +#### Building InnerEye Cloud Solution + +1. Open Visual Studio 2017 + +2. Open InnerEye Cloud.sln to open InnerEye Cloud Solution from the repo cloned. + +3. Once opened, set the solution configuration to x64. + +4. Open Web.config for Microsoft.InnerEye.Azure.Segmentation.API.AppService + +5. Update the following app settings + + ``` + + + + + + + + + ``` + + Here: + + 1. AccoutName is storage Account Name of Azure Stack Hub + 2. StorageConnectionString is Connection string of Azure Stack Hub storage account. + +6. Once this is updated. once the Web.Config for Microsoft.InnerEye.Azure.Segmentation.Worker.AppService + + ``` + + + + + + + + + + + ``` + + Here: + + 1. AccoutName is storage Account Name of Azure Stack Hub + 2. StorageConnectionString is Connection string of Azure Stack Hub storage account. + 3. InferencingContainerEndpoint is IP address of Inferencing container VM + 4. InferencingContainerEndpointPort is port number where Inferencing container is hosted. + +7. When both Web.config files are ready build the solution from the build menu of Visual Studio. + +#### Building Gateway Solution + +1. Open the new instance of Visual Studio 2017 +2. Open Microsoft.Gateway.sln from the Gateway repo cloned. +3. Modify the InnerEyeSegementationClient.cs to add inferencing API endpoint. +4. Set the project configuration to x64 +5. Build the solution from the build menu of Visual Studio. + +### Deploying the solutions + +#### Deploying InnerEye Cloud Solution + +1. Either you can download Publish Profile of deployed Inferencing API or you can also create new one using Visual Studio Publish option. +2. To download publish profile go to Azure Stack Hub portal and open Inferencing API resource. +3. Click on Get Publish profile button from overview page. +4. Once downloaded switch to Visual Studio window which has InnerEye Cloud solution opened. +5. Right click on Microsoft.InnerEye.Azure.Segmentation.API.AppService and select Publish. +6. Import the downloaded publish profile. +7. Set the release configuration to x64 and click publish. +8. This will deploy the Microsoft.InnerEye.Azure.Segmentation.API.AppService to hub. +9. Now switch to browser window and open Inferencing Engine App Service. +10. Once open go to overview page and download the publish profile by clicking on Get Publish Profile button. +11. Once downloaded right click on Microsoft.InnerEye.Azure.Segmentation.Worker.AppService and click publish. +12. Import the downloaded publish profile. +13. Set the release configuration to x64 and click Publish. +14. This will publish Microsoft.InnerEye.Azure.Segmentation.Worker.AppService + +#### Deploying Gateway Solution + +1. Gateway needs to run as Windows Service but you can also run executables of Gateway solution independently. +2. To run gateway from installer copy the build contents from Gateway solution to the Virtual Machine dedicated for Gateway. +3. Search and open Microsoft.InnerEye.Gateway.msi +4. This will install Gateway as Windows Service in virtual machine. +5. If you do not want to run Gateway Services as Windows Service then +6. From the build contents go to Microsoft.InnerEye.Listener.Processor and look for executable. +7. Open the executable file. +8. Go to Microsoft.InnerEye.Listener.Receiver and look for executable. +9. Open the executable. + +# Chapter 3: Testing and Configuring Deployed Environment + +Below are the instructions to debug and monitored resources after the solution has been deployed below. + +## VM: Inferencing container (Linux) + +Once the machine is running, ensure that the _headandneckmodel_ container is running by running `docker ps` and ensuring you have the container "_headandneckmodel_" up. If everything is up you should be able to navigate to http://38.102.181.60:8081/ and see output there. + +If the container is not running or needs to be restarted, run `docker kill` and then do the following: + +1. Run `docker run -it --entrypoint=/bin/bash -p 8081:5000 -e AZURE_STORAGE_ACCOUNT_NAME= -e AZURE_STORAGE_KEY= -e AZURE_STORAGE_ENDPOINT=ppe2.stackpoc.com --gpus all headandneckmodel:latest` +2. Run `conda activate nnenv` +3. Run `python web-api.py` - this should launch the web server that is wrapping the inferencing container + +If the container is already running: +* Use `docker logs ` to retrieve logs from the container shell +* Use `docker attach ` to connect to the interactive shell of the inferencing container + +## Inferencing Engine AppService +Runs model invoker. Makes calls to the model server. Converts from DICOM to NIFTI. + +In sample environment runs on: app-inferapi + +## Inferencing API AppService +Runs API, kicks off model inferencing via communication with the inferencing engine. + +In sample environment runs on: infereng + +## Gateway VM (Windows) + +Gateway should be launched like so: +1. **GatewayProcessor** (C:\Users\azureuser\source\repos\Gateway\Source\Microsoft.Gateway\Microsoft.InnerEye.Listener.Processor\bin\x64\Debug\Microsoft.InnerEye.Listener.Processor.exe) +2. **GatewayReceiver** (C:\Users\azureuser\source\repos\Gateway\Source\Microsoft.Gateway\Microsoft.InnerEye.Listener.Receiver\bin\x64\Debug\Microsoft.InnerEye.Listener.Receiver.exe) + +Before launching make sure that the API App Service is running as the Gateway checks its health first and wouldn't start if it can't find the app service. + +Watch startup logs to make sure there are no errors. Note that the inferencing container should have its server running before the segmentation processor and API are launched. + +### Gateway Configuration + +Gateway configuration files are located in: C:\Users\azureuser\source\repos\Gateway\Source\Microsoft.Gateway\SampleConfigurations +**TODO** Server URL needs to be configured. For now hardcoded in InnerEyeSegmentationClient.cs, EnvironmentBaseAddress + +## Storage account +**Account name**: devstoreaccount1 + +### Containers +Used to store segmentations coming in (converted to NIFTI) and out (converted to NIFTI). One container is created per segmentation request + +#### Tables + +| Name | Purpose | +| ----------------- | ------------------------------------------------------------ | +| gatewayversions | not in use | +| imagesindexes | not in use | +| models | not in use (will be used after model configuration is read from the AML package) | +| progresstable | contains progress per segmentation, updated by cloud code. PartitionKey - model ID; RowKey - segmentation ID | +| rtfeedbackindexes | not in use | +| rtindexes | ?? | +| TimingsTable | Used to compute progress by storing average time of multiple model runs | + +## InnerEye app + +If using InnerEye app, below are some tips on configuration. + +App launched from shortcut on the desktop (Microsoft InnerEye Dev). App configuration is stored in _%APPDATA%\Microsoft\InnerEye\MicrosoftInnerEyeWPFDevexeSettings.json_. + +Relevant configuration parameters are: +* DcmReceiveFolder - where the received rtstruct or images are stored +* UserDefinedPushLocations - configured send destinations (also can be configured through the app) +* DcmAeTitle - AET of the DICOM server that app is running +* DcmPort - port for the app's DICOM server + +You can check that the app can talk to the gateway by going to app setting (gear icon in the bottom left) and pressing "Echo" next to the endpoint corresponding to the gateway. If successful, the gateway should show success message in the notification bar and the gateway receiver console log should show informational log messages as well. + +Test H&N volume is located in _C:\Users\azureuser\Test Images\headandneck_ + +# Chapter 4. Running the Demo + +In order to run the demo, ensure that all services are running by launching them in the following order: + +1. Inferencing container and API +2. Gateway + +Then do the following if using the InnerEye app: +1. Launch the InnerEye app. You can use the echo to test if there is connectivity with the gateway. +2. Then, open the image (test H&N volume is located in _C:\Users\azureuser\Test Images\headandneck_) and ensure you can see it in the app +3. Select '...' button that is shown when hovering over image in the explorer panel, select "Export", choose destination, check "Send Series", make sure that "Send Structure Set" is unchecked, and click "Export". +4. Observe the console output from solution components to ensure that the process is underway. Once process is complete you should see a yellow "new image received" icon in the left sidebar of the app. +5. Go into the Incoming tab on the app and press "Open" next to the latest segmentation received. \ No newline at end of file diff --git a/Docs/Additional Documents/VideoAndBlogs.md b/Docs/Additional Documents/VideoAndBlogs.md new file mode 100644 index 0000000..7edeeed --- /dev/null +++ b/Docs/Additional Documents/VideoAndBlogs.md @@ -0,0 +1,3 @@ +# Video and Blogs + +@Michela \ No newline at end of file diff --git a/Docs/Diagram.md b/Docs/Diagram.md new file mode 100644 index 0000000..e2c0880 --- /dev/null +++ b/Docs/Diagram.md @@ -0,0 +1,39 @@ +# Editing Sequence Diagram + +The [sequence diagram](sequence.wsd) is stored in the [PlantUML](https://plantuml.com/) text format. + +## PlantUML + +To build a png image file from the wsd file, follow the PlantUML [Getting Started](https://plantuml.com/starting): + +1. Install [Java](https://www.java.com/en/download/). + +1. Download [plantuml.jar](http://sourceforge.net/projects/plantuml/files/plantuml.jar/download) + +### Preview + +To show a live preview of the image whilst editting the file: + +1. Run the command: + +```cmd +java -jar plantuml.jar +``` + +2. Change the file extensions text box to show "wsd" + +1. Select "sequence.png [sequence.wsd]" in the file list. + +1. A preview of the image will be shown in a new window and will update automatically as the text file is editted. + +### Export + +To create an image from the file run the command: + +```cmd +java -jar plantuml.jar sequence.wsd +``` + +## Visual Studio Code + +There are PlantUML extension available for [Visual Studio Code](https://code.visualstudio.com/), for example [PlantUML](https://marketplace.visualstudio.com/items?itemName=jebbs.plantuml) which offer previews of the final image and image export functions. diff --git a/Docs/Help and Bugs Reporting/BugReporting.md b/Docs/Help and Bugs Reporting/BugReporting.md new file mode 100644 index 0000000..ffd7394 --- /dev/null +++ b/Docs/Help and Bugs Reporting/BugReporting.md @@ -0,0 +1,4 @@ +# Guidelines for how to report bug + +@Mark, @Ivan + diff --git a/Docs/Help and Bugs Reporting/PullRequestsGuidelines.md b/Docs/Help and Bugs Reporting/PullRequestsGuidelines.md new file mode 100644 index 0000000..c5f6c8b --- /dev/null +++ b/Docs/Help and Bugs Reporting/PullRequestsGuidelines.md @@ -0,0 +1,3 @@ +# Pull Requests guidelines + +@Mark, @Ivan \ No newline at end of file diff --git a/Docs/Help and Bugs Reporting/Stack-overflowAndOtherChannels.md b/Docs/Help and Bugs Reporting/Stack-overflowAndOtherChannels.md new file mode 100644 index 0000000..961a9c3 --- /dev/null +++ b/Docs/Help and Bugs Reporting/Stack-overflowAndOtherChannels.md @@ -0,0 +1,3 @@ +# Tag to be use on stack-overflow and other channels + +@Mark, @Ivan \ No newline at end of file diff --git a/Docs/environment.png b/Docs/environment.png new file mode 100644 index 0000000..9f3acbe --- /dev/null +++ b/Docs/environment.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af212da614c9a9b1cc10fa2d634c49bbdf73500c8c6611296024d051d09f5b7e +size 336704 diff --git a/Docs/sequence.png b/Docs/sequence.png new file mode 100644 index 0000000..adce829 --- /dev/null +++ b/Docs/sequence.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a92a3797b3e8589be3dcdef1efa234b06d3f2fa6e0f10eb3ac2f2be2cb8561c6 +size 53677 diff --git a/Docs/sequence.wsd b/Docs/sequence.wsd new file mode 100644 index 0000000..3a555f6 --- /dev/null +++ b/Docs/sequence.wsd @@ -0,0 +1,59 @@ +@startuml sequence +skinparam backgroundColor #EEEBDC +skinparam handwritten false +participant "DICOM Client" as dc +participant "Receiver Service" as rt +database "RootDicomFolder" as db +queue "Upload Queue" as uq +participant "Upload Service" as ut +participant "Inference Web Service" as is +queue "Download Queue" as dq +participant "Download Service" as dt +queue "Push Queue" as pq +participant "Push Service" as pt +participant "Destination" as dd +queue "Delete Queue" as xq +participant "Delete Service" as xt +activate dc +dc -> rt: Assoc Req +activate rt +loop +dc -> rt: C-Store +rt -> db: Save DICOM files +end +dc -> rt: Assoc Release +deactivate dc +rt -> uq: DICOM files rec'd +uq -> ut +deactivate rt +activate ut +db -> ut: Load DICOM files +ut -> is: POST anonymised files +activate is +ut -> dq: Uploaded +dq -> dt +activate dt +ut -> xq: Delete DICOM files +xq -> xt +deactivate ut +activate xt +xt -> db: Delete DICOM files +deactivate xt +is -> dt: Download DICOM-RT +deactivate is +dt -> db: Save DICOM-RT +dt -> pq: Downloaded +pq -> pt +deactivate dt +activate pt +db -> pt: Load DICOM-RT +activate dd +pt -> dd: Send DICOM-RT +deactivate dd +pt -> xq: Delete DICOM-RT file +xq -> xt +deactivate pt +activate xt +xt -> db: Delete DICOM-RT file +deactivate xt +@enduml diff --git a/GeoPol.xml b/GeoPol.xml new file mode 100644 index 0000000..ff6b5e9 --- /dev/null +++ b/GeoPol.xml @@ -0,0 +1,23 @@ + + + + + +]> + + + +&GitRepoName; + + + + . + + + + + .gitignore + GeoPol.xml + + diff --git a/Images/1ValidSmall/1.dcm b/Images/1ValidSmall/1.dcm new file mode 100644 index 0000000..af5d96e --- /dev/null +++ b/Images/1ValidSmall/1.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50fff99a192f9edcdabd8ff038eb9b435f61bc4ffcccb8f2a386e7e1e5984b9c +size 526478 diff --git a/Images/1ValidSmall/10.dcm b/Images/1ValidSmall/10.dcm new file mode 100644 index 0000000..aee9b5d --- /dev/null +++ b/Images/1ValidSmall/10.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce46b92e238dbac3a216ee46b9ef33bb0627394d55f121a17fa551a1bd3cc469 +size 526478 diff --git a/Images/1ValidSmall/11.dcm b/Images/1ValidSmall/11.dcm new file mode 100644 index 0000000..9c2db48 --- /dev/null +++ b/Images/1ValidSmall/11.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:34281b6bdea4caf78c2697f01f1905c959bd7ab7369ed4857d711cc51d6ae999 +size 526474 diff --git a/Images/1ValidSmall/12.dcm b/Images/1ValidSmall/12.dcm new file mode 100644 index 0000000..1d51d29 --- /dev/null +++ b/Images/1ValidSmall/12.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:805e97d1fc2146fd1b2afe2673a3c9435d77a2465d0b20968067b4bd5638d29d +size 526474 diff --git a/Images/1ValidSmall/13.dcm b/Images/1ValidSmall/13.dcm new file mode 100644 index 0000000..1e849dd --- /dev/null +++ b/Images/1ValidSmall/13.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8670e931652af0a874946b926fd26d184a7223f57a52a9462149932991b67dcf +size 526474 diff --git a/Images/1ValidSmall/14.dcm b/Images/1ValidSmall/14.dcm new file mode 100644 index 0000000..f0a21a8 --- /dev/null +++ b/Images/1ValidSmall/14.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:685a803e57d4c184706ade300a8168ec247aec008410a2ab853f2df2dbe69c79 +size 526474 diff --git a/Images/1ValidSmall/15.dcm b/Images/1ValidSmall/15.dcm new file mode 100644 index 0000000..7687337 --- /dev/null +++ b/Images/1ValidSmall/15.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0241f30d2e1bacacf20212d34f3845ea75b783e6012eec5f266ed449260a35e4 +size 526474 diff --git a/Images/1ValidSmall/16.dcm b/Images/1ValidSmall/16.dcm new file mode 100644 index 0000000..3a557ca --- /dev/null +++ b/Images/1ValidSmall/16.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bcf90239ba20f1983f8dd41eeb3c4f189b5e119326115835e26401e68394dad6 +size 526474 diff --git a/Images/1ValidSmall/17.dcm b/Images/1ValidSmall/17.dcm new file mode 100644 index 0000000..0ff75cd --- /dev/null +++ b/Images/1ValidSmall/17.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c016e1925104e799cbea3d2878ecb9a7da2bfb2e11356a10d87fc3ca3ca74448 +size 526474 diff --git a/Images/1ValidSmall/18.dcm b/Images/1ValidSmall/18.dcm new file mode 100644 index 0000000..7bef66e --- /dev/null +++ b/Images/1ValidSmall/18.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d3bbd8d7827c32df6d993fbeec0e98b3809c12b1de9b875817cc98142962f88f +size 526474 diff --git a/Images/1ValidSmall/19.dcm b/Images/1ValidSmall/19.dcm new file mode 100644 index 0000000..aa62baa --- /dev/null +++ b/Images/1ValidSmall/19.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b85fc06be70ef858c8d53e3eb85f715f19cfcec25102a19cee77d97f900e276 +size 526474 diff --git a/Images/1ValidSmall/2.dcm b/Images/1ValidSmall/2.dcm new file mode 100644 index 0000000..19108e3 --- /dev/null +++ b/Images/1ValidSmall/2.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:24eec6223db0a2e65e8da9cb885a1e02e0f51141dad5d3519724826de5e8182c +size 526478 diff --git a/Images/1ValidSmall/20.dcm b/Images/1ValidSmall/20.dcm new file mode 100644 index 0000000..6287730 --- /dev/null +++ b/Images/1ValidSmall/20.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d6d8a912b6b1721c249144f294ec1b6ead149e843d29d820054894f51118f055 +size 526474 diff --git a/Images/1ValidSmall/3.dcm b/Images/1ValidSmall/3.dcm new file mode 100644 index 0000000..8420153 --- /dev/null +++ b/Images/1ValidSmall/3.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:10d1123758ef1e55f1a16d6f6e0af188ebc55afc17d6e44479bb23bb2ce78fbf +size 526478 diff --git a/Images/1ValidSmall/4.dcm b/Images/1ValidSmall/4.dcm new file mode 100644 index 0000000..21a4e8b --- /dev/null +++ b/Images/1ValidSmall/4.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2e460817f9edb30557fb270b807c682602f25817474f28e0e28c14ac54234d9 +size 526478 diff --git a/Images/1ValidSmall/5.dcm b/Images/1ValidSmall/5.dcm new file mode 100644 index 0000000..d59c143 --- /dev/null +++ b/Images/1ValidSmall/5.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bb0397440c0563af5993b67cde489fc4786eeb0c971efb4f5b1115f742a5a5bd +size 526478 diff --git a/Images/1ValidSmall/6.dcm b/Images/1ValidSmall/6.dcm new file mode 100644 index 0000000..f8e1d2b --- /dev/null +++ b/Images/1ValidSmall/6.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:71486a9fd6f0bdff81c95b3b9b2569c226bd58449a68f0c136a6b9c701a93076 +size 526478 diff --git a/Images/1ValidSmall/7.dcm b/Images/1ValidSmall/7.dcm new file mode 100644 index 0000000..7378cf5 --- /dev/null +++ b/Images/1ValidSmall/7.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:19dee5cc07449c6475dd6e9ea41fbbde1907baad932f350cee3cfc2932fe6f4c +size 526478 diff --git a/Images/1ValidSmall/8.dcm b/Images/1ValidSmall/8.dcm new file mode 100644 index 0000000..d57a15a --- /dev/null +++ b/Images/1ValidSmall/8.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b0495371764b38fc9b556e8fa6d41ea2153f8d33b375933d74b42349b0b1a1f5 +size 526478 diff --git a/Images/1ValidSmall/9.dcm b/Images/1ValidSmall/9.dcm new file mode 100644 index 0000000..f70ce24 --- /dev/null +++ b/Images/1ValidSmall/9.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d60a33d88c971c0eef7edccc57e6889b1a25308b0f62a98de5d8d66d25016fc2 +size 526478 diff --git a/Images/InvalidPN/1.dcm b/Images/InvalidPN/1.dcm new file mode 100644 index 0000000..f88ccd3 --- /dev/null +++ b/Images/InvalidPN/1.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64d39c799ebc9e9fc29a0a2f6dcf6a06fc650de3cc1ff8e2dfb7198e07598a3b +size 526044 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1090460865966542526486876149643126065.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1090460865966542526486876149643126065.dcm new file mode 100644 index 0000000..64795c2 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1090460865966542526486876149643126065.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:567fde5610490605118bbddf206f2dce8fe4eaabe9f0767c59be45e2e8b6e435 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1113583911936106161255831110435725437.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1113583911936106161255831110435725437.dcm new file mode 100644 index 0000000..338a668 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1113583911936106161255831110435725437.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3484c46aad0bbd9e42b1499bc69b30c10138aef75c68630e7c73b8b5895ed115 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1247728137096394998404446793947041469.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1247728137096394998404446793947041469.dcm new file mode 100644 index 0000000..e61ba71 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1247728137096394998404446793947041469.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b180028584029ceeda6c4fcd1113f0dacd05e8c1f3d8f55e6c142dcf2ff29047 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1268297749850909254845172237211472094.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1268297749850909254845172237211472094.dcm new file mode 100644 index 0000000..668de2d --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1268297749850909254845172237211472094.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64979b699d117b74fab0c36650d42361c71e1a706a913555e4b1dc825239722c +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276409595806088158265421354038204673.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276409595806088158265421354038204673.dcm new file mode 100644 index 0000000..ddb92ff --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276409595806088158265421354038204673.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fc2cfc9f2586765ca38b9f693d0ff93acfd30f90e2f89f55d14da2cfe49d668 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276823266322239590774784606318904757.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276823266322239590774784606318904757.dcm new file mode 100644 index 0000000..0cc19e4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1276823266322239590774784606318904757.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:45ec592478ce37f75076de21b96b3d534d6aff39e40c8613cd78770e6538f965 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1309493738860257695938496641148832585.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1309493738860257695938496641148832585.dcm new file mode 100644 index 0000000..620c1ce --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1309493738860257695938496641148832585.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:29fe517282a6e78c389761331e27c23c1334004cec4eed9faab13251e656f2f5 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1315228061378753091775225585475513904.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1315228061378753091775225585475513904.dcm new file mode 100644 index 0000000..2b84791 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1315228061378753091775225585475513904.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d4a6fb0d1d34256cdeaca408de5d58163bf4323191ce01335eb8782bbaa1861d +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1424821720661730651575121131022380064.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1424821720661730651575121131022380064.dcm new file mode 100644 index 0000000..3e485f6 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1424821720661730651575121131022380064.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:036c969bed6550442b8eb92993b3041c2888584db0ba440686433d646ae892fa +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.149540744391437319866380830011665495.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.149540744391437319866380830011665495.dcm new file mode 100644 index 0000000..7524962 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.149540744391437319866380830011665495.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4b8a4a72c61aa8f79235e1cfa48f079c69d249c648a5b679d030e110987bbfa +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1504292588464778016131047913246200712.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1504292588464778016131047913246200712.dcm new file mode 100644 index 0000000..4493364 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1504292588464778016131047913246200712.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ce4cc10c58ccb3500ce744341ef4291659119950483ae52115aedba4f1a9316c +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1586289423697072709053282986003052130.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1586289423697072709053282986003052130.dcm new file mode 100644 index 0000000..864a8eb --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1586289423697072709053282986003052130.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a3772cd4a47c4719466d4c28899f29a3ed9c5a9b48b48809b49af277e097c8a +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603507555357901003873369762405395158.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603507555357901003873369762405395158.dcm new file mode 100644 index 0000000..8af916d --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603507555357901003873369762405395158.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f40c5a4b051322eb9b43a24b26cd620e1fa6d54d8ede7da78643583b2a13cd3b +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603941769404672370986212481856666706.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603941769404672370986212481856666706.dcm new file mode 100644 index 0000000..14043db --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1603941769404672370986212481856666706.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c52c09ba22103b9f4c89116e990ff93b7fd45c58fa0a80ac3181f95fdaf0f43 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1733723835157503707471772250948338056.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1733723835157503707471772250948338056.dcm new file mode 100644 index 0000000..92d35f0 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.1733723835157503707471772250948338056.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:131b552658886547667cb9693e5163f32852dd90be9a16387051903eba654c84 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.177159971429155354323352122369910018.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.177159971429155354323352122369910018.dcm new file mode 100644 index 0000000..f8039e6 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.177159971429155354323352122369910018.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5513a440245c4ac180c61b8b13d4b67be8cf275acdb5c80620c9840a2de55b15 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2065039152027214948185192787016609102.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2065039152027214948185192787016609102.dcm new file mode 100644 index 0000000..6bb5c34 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2065039152027214948185192787016609102.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6f3d79a22dab1f7022f5aec471de9939193b44adbebac7304477c4977e3ef2f5 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2077707482707913294090849361486537575.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2077707482707913294090849361486537575.dcm new file mode 100644 index 0000000..0664dab --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2077707482707913294090849361486537575.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:109e22ee9fda4b86a77493a89b496b3577eb2ad60ba98229de53576fce71597a +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2091056545198481824972740777538855628.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2091056545198481824972740777538855628.dcm new file mode 100644 index 0000000..62a1097 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2091056545198481824972740777538855628.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f4d73819684de029e03b534074169754f6e9ea70031f75e56dcba70959b0f2a9 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2300191907444461623117367828109639362.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2300191907444461623117367828109639362.dcm new file mode 100644 index 0000000..addf1d8 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2300191907444461623117367828109639362.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f9f2ea65a9beb02d982190aaf03a82581dcaacb99eaaedc2c5511bea6b4a0cb +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2311513203284923046703814127142600862.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2311513203284923046703814127142600862.dcm new file mode 100644 index 0000000..b6c337c --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2311513203284923046703814127142600862.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4c28b9926924a0a3ce3278e199bb796ceed5b4cea34cd6043cea5f467f328499 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.234848398285035287627993400411403947.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.234848398285035287627993400411403947.dcm new file mode 100644 index 0000000..76f306b --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.234848398285035287627993400411403947.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14c862cfe8d62504fe2c52fda80a355c544df8a8b9272d9b41abd83478c9c4e7 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2366245601673390599661173419249430152.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2366245601673390599661173419249430152.dcm new file mode 100644 index 0000000..9004270 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2366245601673390599661173419249430152.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b6bdfe9d7b2e498ca4d515a8842b253da95a41de31287d2b21592e8d3eb4e25e +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2517875113743844279077466488297548492.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2517875113743844279077466488297548492.dcm new file mode 100644 index 0000000..a948506 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2517875113743844279077466488297548492.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e7aad5900ce129243b1e2e204c880023061da2a571cc990e0acf99a17f59a8d6 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2528918310531862024240590111330526032.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2528918310531862024240590111330526032.dcm new file mode 100644 index 0000000..60c0b96 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2528918310531862024240590111330526032.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9b92f215847c1d9afbb9758325539f799c3c30165c4d73d9f05030bf93c5be18 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.255472593933636444021718369425699674.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.255472593933636444021718369425699674.dcm new file mode 100644 index 0000000..9077ffa --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.255472593933636444021718369425699674.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bff8bb3367c76a493c60d7d1165db7a3a85e10be1312e082ea656e09f9b003bb +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.272404100495827097094503485633903342.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.272404100495827097094503485633903342.dcm new file mode 100644 index 0000000..01ab6fd --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.272404100495827097094503485633903342.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:53b38404d3e048fa76d0bfb98dfb7e8fc150f231d9de37d32aa431e9e0f7901f +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2856276318351938990833279631467510060.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2856276318351938990833279631467510060.dcm new file mode 100644 index 0000000..2c8d32a --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2856276318351938990833279631467510060.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2dfc19d8d7be630e924a5e84fdb83818976240610c0de4ea899d76342612481 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.28621440201171685228804903937000813.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.28621440201171685228804903937000813.dcm new file mode 100644 index 0000000..7c02ba1 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.28621440201171685228804903937000813.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c88ba0663232d13b3c2327a6b4211c74c83ac8ef84e75132547fb62d6d76f3f9 +size 150720 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2929087666691054004992797698806946100.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2929087666691054004992797698806946100.dcm new file mode 100644 index 0000000..ec387b0 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2929087666691054004992797698806946100.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:986964ae0e2e4549165716ae0b61c0027ec1c387961fc6a7fbad242a2824388e +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2965520600218423570825909564893574418.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2965520600218423570825909564893574418.dcm new file mode 100644 index 0000000..ceaf810 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.2965520600218423570825909564893574418.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:73d16ad51f35c4b723f5e9c06447a7f90146ae3494624b117b299cfe2ea41cbf +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3039200638329081880003952315701223755.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3039200638329081880003952315701223755.dcm new file mode 100644 index 0000000..8f0a8b4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3039200638329081880003952315701223755.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:55e98faac37af15cd1cb1343023ad9555b93fb2c946dcdb64cfce864d139e5df +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3138565465547482227814490173133974101.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3138565465547482227814490173133974101.dcm new file mode 100644 index 0000000..ce7cdd4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3138565465547482227814490173133974101.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b22259d183cfacdef0d1e2571456b078c07cd1a49fe8ac5ed024c80aee9f0b4 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3264163721230795368390441633568856169.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3264163721230795368390441633568856169.dcm new file mode 100644 index 0000000..873ebba --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3264163721230795368390441633568856169.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e35564dc39c13bf4903edde3207446723f253b24a889af71b5a78e39efef1a10 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3329214252684256799918043573009559701.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3329214252684256799918043573009559701.dcm new file mode 100644 index 0000000..399220d --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3329214252684256799918043573009559701.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6e98977f36d2b51939160d282a6ecb350e8c8646fde405dfb0f2cd60e848e85e +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3342450797385800689172373508778910683.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3342450797385800689172373508778910683.dcm new file mode 100644 index 0000000..bae9b34 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3342450797385800689172373508778910683.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:611e4337963323d1c5f0efd80c43e63c40b52b078293a5c3521e5f501b2d9d18 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3348092881200381409012471499478647143.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3348092881200381409012471499478647143.dcm new file mode 100644 index 0000000..f4ad876 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3348092881200381409012471499478647143.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6921f1f016e02c9fd029a4055ca00b5cd1eb9c2466279d0021f34173b7e75eb +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3456195802144665971608371497453372482.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3456195802144665971608371497453372482.dcm new file mode 100644 index 0000000..648b438 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3456195802144665971608371497453372482.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5a4c9b44ced9d2844938e386627a52dc2a52229b5bf947fc3a5b53947692adb +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3531577771862774762674919299088992667.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3531577771862774762674919299088992667.dcm new file mode 100644 index 0000000..76f3411 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3531577771862774762674919299088992667.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:130ea08b7bde08b4ec3511e612af413ac823aabef999b756e82621fdbffc15ee +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3559519125404573210574469451974196707.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3559519125404573210574469451974196707.dcm new file mode 100644 index 0000000..8b806ab --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3559519125404573210574469451974196707.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6cbf53909047517c1afda77aeee69734d134d22cb5c91a95fba9d6357d080c8 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3707432864349768500524190555289963771.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3707432864349768500524190555289963771.dcm new file mode 100644 index 0000000..1243455 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3707432864349768500524190555289963771.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a3e15178a4fb21f71dafdf1791eec5255ae201481a92e447e63b0442d8433f95 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3826387874733181325715780105257912381.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3826387874733181325715780105257912381.dcm new file mode 100644 index 0000000..cd0a568 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3826387874733181325715780105257912381.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e2f18ef59a3377f791866b5b63e1e60631a5db9c7777ed611ec08e6ea857a77f +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3884266638195393445767846913412404340.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3884266638195393445767846913412404340.dcm new file mode 100644 index 0000000..fbf6048 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3884266638195393445767846913412404340.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:00d73ab54cceb6a2c8e518c61fdf64e1814bfcbc1db70f4bf17052e080947a52 +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3901472286358631291254773359964022615.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3901472286358631291254773359964022615.dcm new file mode 100644 index 0000000..fcd1a63 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.3901472286358631291254773359964022615.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d37b52c31a7e53bcb563f4d4cefc37d6e7f6ddd2e731ca8b821a41cfb451112 +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077627525916031544392875981537574880.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077627525916031544392875981537574880.dcm new file mode 100644 index 0000000..295a957 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077627525916031544392875981537574880.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e59139d5e805782cd664e8684986d8e875138616753b96f3115dbd8d8bc52d04 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077765416632201049866028033810645876.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077765416632201049866028033810645876.dcm new file mode 100644 index 0000000..ed225f9 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4077765416632201049866028033810645876.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:915328cdbabb0d4d1b709620bd9cf4e3b9a6d91a0374097423e14a5845ca4f32 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.416267245062537504426664884554455695.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.416267245062537504426664884554455695.dcm new file mode 100644 index 0000000..f326add --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.416267245062537504426664884554455695.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94f7e9a9558e2778acab95feb059f5e84d9a04a975a1d5a771ca1512b98496a5 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4220429893402217707626563486809748036.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4220429893402217707626563486809748036.dcm new file mode 100644 index 0000000..7a67e51 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4220429893402217707626563486809748036.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6449ccf9319c70c6e93b59a2578a4fea18942ff15e7574d18b073b49a80f64ca +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4227760063788177814850539832569856657.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4227760063788177814850539832569856657.dcm new file mode 100644 index 0000000..2b6e33e --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4227760063788177814850539832569856657.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac1efeb4d3ec9ac4c82b16dcc5fc8c9e9c1b6c6ede6ad5281f16083c70d8f8f3 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4361110441258480612761531589119880729.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4361110441258480612761531589119880729.dcm new file mode 100644 index 0000000..041d252 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4361110441258480612761531589119880729.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2dc7d1d03db10130cb053559cc5e7017514bdb6f57ad46283bb7733a89075a8b +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4429117347680799243274407179054009519.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4429117347680799243274407179054009519.dcm new file mode 100644 index 0000000..20a4e56 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4429117347680799243274407179054009519.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b3b3f777e8e3b4fe33ae6ca066eb0967cabd110bc3bfb924f879180634f8ee0 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4430789403887947931838641862616347273.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4430789403887947931838641862616347273.dcm new file mode 100644 index 0000000..6a41ab2 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4430789403887947931838641862616347273.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:85f24ac9e0f55c397b4d2708ff7b686a287dc46c2ec506b0a7ca7e8899391f3f +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4545923077608497415467351747154540694.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4545923077608497415467351747154540694.dcm new file mode 100644 index 0000000..14a839c --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4545923077608497415467351747154540694.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9edfd30f552172560e6f11f68cb120aa1a1b80d1022342c986caa1abaa088d99 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.457099872952531787279666134580381486.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.457099872952531787279666134580381486.dcm new file mode 100644 index 0000000..452e97f --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.457099872952531787279666134580381486.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39e26c9b62fee9c588b65cfcb13028a602501869586e34c845952215fba31e36 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4583195912092827698588179857276488969.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4583195912092827698588179857276488969.dcm new file mode 100644 index 0000000..f8f1072 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4583195912092827698588179857276488969.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b06e81e69cd27af0adb281563f5f8485d4f8ad5b8262d23a622e8ea0012b1326 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4593469450985571044589247082565455469.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4593469450985571044589247082565455469.dcm new file mode 100644 index 0000000..fb53c91 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4593469450985571044589247082565455469.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9eb5f6cdb17b699188e5aac5e310f680c46557e74d7fe52e2dd850775a6080eb +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4661186853222973408651088617727253235.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4661186853222973408651088617727253235.dcm new file mode 100644 index 0000000..6d7b037 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4661186853222973408651088617727253235.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c60581abe1f45bd1b92c021aecf4a1de9c65efbdcd9189bea4021ee8e8168e6 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4678179273222049229856606186006082767.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4678179273222049229856606186006082767.dcm new file mode 100644 index 0000000..78aaa39 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4678179273222049229856606186006082767.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:91e7ecc9f5111d07f61c243547ae722dcf366126f8b46458de81c7a96771111b +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4714785946536100308562944102363288971.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4714785946536100308562944102363288971.dcm new file mode 100644 index 0000000..5f8f270 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4714785946536100308562944102363288971.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e09cf34cc7f9e3a5fc939ef3aa96e62babcd96b5602400a5ac57e0c420663ffc +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4766066604650145714566023185265207574.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4766066604650145714566023185265207574.dcm new file mode 100644 index 0000000..0ab086b --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4766066604650145714566023185265207574.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd146ded38c94c2bc09652ea8d2de6467a7f1236cb4f29dadb6bc3b0c79ce5df +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4786946907986646372976065107744927432.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4786946907986646372976065107744927432.dcm new file mode 100644 index 0000000..f1a914b --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4786946907986646372976065107744927432.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f2ae1a1630be030527928079adcab3dc1ce97bb613b9ed5fa02487463c32d6c2 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.479594740413779986935166935711464664.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.479594740413779986935166935711464664.dcm new file mode 100644 index 0000000..740acef --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.479594740413779986935166935711464664.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:013c312518778ad5d93f79291254256773d1c378cc000092fccca838e25df7dc +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4820400408130735437856746766113162355.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4820400408130735437856746766113162355.dcm new file mode 100644 index 0000000..2eb2622 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4820400408130735437856746766113162355.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe193837530dec2eccdd14102b754fe476f8166b48296215a3148f2ab79cbf78 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4847684476803386246306224608512829862.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4847684476803386246306224608512829862.dcm new file mode 100644 index 0000000..39551df --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4847684476803386246306224608512829862.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b41429fc800040515044353346b8388d7d73924b21de815304b87a08affb5951 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4848358862206565187800419652953160929.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4848358862206565187800419652953160929.dcm new file mode 100644 index 0000000..0ce8670 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4848358862206565187800419652953160929.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:493b72ab5cf5597dde2fe21d627f741bf46ff1398ad8d4209ea720b6a101cae7 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4867098704721969152550959342284226672.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4867098704721969152550959342284226672.dcm new file mode 100644 index 0000000..b88fd62 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4867098704721969152550959342284226672.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac51db2feb81c4a1b7480b45b1cab548c52cf32ba2ee2d3e90fa719660da5f8c +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4974603962485423328908158546094390835.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4974603962485423328908158546094390835.dcm new file mode 100644 index 0000000..8cbb57c --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.4974603962485423328908158546094390835.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42bfc4e02d1a7c53d8b9afffb2fbef62b1290e067d1bf3b5786477be79c0d506 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5032568314730441475611031612232702813.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5032568314730441475611031612232702813.dcm new file mode 100644 index 0000000..7f36024 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5032568314730441475611031612232702813.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:95c268a97d20426f0fd5421e91c1a125c937471dac09e8880116e3ba30a510fd +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5117600506941670944922941183791284804.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5117600506941670944922941183791284804.dcm new file mode 100644 index 0000000..33eafda --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5117600506941670944922941183791284804.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b97df17e8bb8fcf636e8662c1ae344369b4d1c165d8e646b4a5debd513ca786b +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.514204938254240720422969359500861281.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.514204938254240720422969359500861281.dcm new file mode 100644 index 0000000..ee8a732 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.514204938254240720422969359500861281.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c51c1cd4f65a03bed3b374700696df5940211a0f690012b5f6c7e7550e433f6a +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5168782564514843969477858686535623503.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5168782564514843969477858686535623503.dcm new file mode 100644 index 0000000..26ddfc6 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5168782564514843969477858686535623503.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8014ecd8ca1b7224a7ea54c8e67a8d5850005e2ef17cdf467738198904aa91db +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5214260960761315974879308927152578120.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5214260960761315974879308927152578120.dcm new file mode 100644 index 0000000..61eb09c --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5214260960761315974879308927152578120.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31c1082e5367fbc908addb22804e2d007359541838e491629667bb7500b5868f +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5353352661842228977009124604457342114.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5353352661842228977009124604457342114.dcm new file mode 100644 index 0000000..0420d58 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5353352661842228977009124604457342114.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b7d99436586906223b424695315a8033f4d2d158320408ff80a3ada3d39935a +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5375120196677187954364257785394062354.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5375120196677187954364257785394062354.dcm new file mode 100644 index 0000000..174a000 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5375120196677187954364257785394062354.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:246d2afef5576907f4f0caf29d1b642dc59a0d47d3f3da3878996488182385a7 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5480675569475985780063595571287169751.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5480675569475985780063595571287169751.dcm new file mode 100644 index 0000000..1685564 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5480675569475985780063595571287169751.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:68b918aaec403c545493b5fac7e5e9f111fa324c76eb092e3f6cccc756a072f4 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5508569889704436607767594729119990566.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5508569889704436607767594729119990566.dcm new file mode 100644 index 0000000..a202954 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5508569889704436607767594729119990566.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f5ad37c0814ec14d6dff68dae816358eab1c0484a52f3d084d175890871ba90 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5521222100884173015423683049999264236.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5521222100884173015423683049999264236.dcm new file mode 100644 index 0000000..7c42c42 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5521222100884173015423683049999264236.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e3d4ee7b53e7bd4d26b1c5003a2442d0c63c7bc4f04acfb0cafdc2201a752473 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.560743097336335340588260575577289706.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.560743097336335340588260575577289706.dcm new file mode 100644 index 0000000..e63bb2a --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.560743097336335340588260575577289706.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2aee14179b06b0e07135fca53f2b26dc103cbbf16908c05762e6782de444ec3c +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5639810407786340123860181536716326619.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5639810407786340123860181536716326619.dcm new file mode 100644 index 0000000..3f0901d --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5639810407786340123860181536716326619.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:550ef18f659ae58c4c892b6407039377e1e101eba230759a1c84d0a94a10a692 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5692514868855490639520084349215228577.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5692514868855490639520084349215228577.dcm new file mode 100644 index 0000000..07282f2 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5692514868855490639520084349215228577.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:124d006c5fca1999b8e029aa27993239f13a00503e20bdce44fcef891717d2c3 +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5719853188451023087019542019110756484.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5719853188451023087019542019110756484.dcm new file mode 100644 index 0000000..3f19f62 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5719853188451023087019542019110756484.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96bbf793e620679319161363a8c7e4e01aa55051aa0355b95408889d70e0eb07 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5781653050812637054888127655964957862.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5781653050812637054888127655964957862.dcm new file mode 100644 index 0000000..8126ea8 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5781653050812637054888127655964957862.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc4c6912e455f5cded86d5a18bd05de959b9905cf0048c7d29483dfb04ca0a36 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5812286521333908336594040927274738067.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5812286521333908336594040927274738067.dcm new file mode 100644 index 0000000..5fd97f3 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5812286521333908336594040927274738067.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:12e3f52739f5a95d3df1cea9c1e284e953d6d9c117c3e1f21e4ff7eea99ac1ba +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5880874384526854499475562779275913966.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5880874384526854499475562779275913966.dcm new file mode 100644 index 0000000..d7ce7a8 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.5880874384526854499475562779275913966.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d47cf046b9bf7b1356752298127014f8b545daecb534042791ba3bf3c3ddce3f +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.593843373509326635866104994225044111.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.593843373509326635866104994225044111.dcm new file mode 100644 index 0000000..6400404 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.593843373509326635866104994225044111.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:090921778c6718be0c0c29863536ac5170391649de2a4249a0c534f28d6061aa +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.613264099494174407778943900285243255.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.613264099494174407778943900285243255.dcm new file mode 100644 index 0000000..6911166 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.613264099494174407778943900285243255.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9551907cbdeccf22735f651d1a670871f4ce5983c240cacb6f83cdd2933cbe07 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6143004276963640907894318138477941197.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6143004276963640907894318138477941197.dcm new file mode 100644 index 0000000..46c86a6 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6143004276963640907894318138477941197.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f94260da093dbb1059db025e6ef0eb8c8f86444889530ccc85006c594e2dcda8 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6168033529901995753477563663928741494.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6168033529901995753477563663928741494.dcm new file mode 100644 index 0000000..3310c38 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6168033529901995753477563663928741494.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2e347f86425b8aff0a157e60db4cc661083ea3ce3eb16a8d672c37a8640f6c4 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6169888496205029341086828801262283710.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6169888496205029341086828801262283710.dcm new file mode 100644 index 0000000..a220042 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6169888496205029341086828801262283710.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:503c968d7c8dee053d62637ab9dd125e892786433dc5cc5ad69e972c063cbb60 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6254149428289287983397667937025799347.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6254149428289287983397667937025799347.dcm new file mode 100644 index 0000000..aa5d0f9 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6254149428289287983397667937025799347.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40bd6c198d17f65537f8fbd3765862c2d7b249f638b96f73f8681930b86d9991 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633777431005242128814320303450183171.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633777431005242128814320303450183171.dcm new file mode 100644 index 0000000..073e9de --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633777431005242128814320303450183171.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b736df6c8032c65911d1936e7283eeb1b52dfd4d765ee254e4dce10d1909aa33 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633912715182934771967647933151861974.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633912715182934771967647933151861974.dcm new file mode 100644 index 0000000..8cfbee3 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.633912715182934771967647933151861974.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:485ee2d3016adb7029f5c6a0a1f99b641c4a8ca4f914d47a11f015d9cbc8dd76 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6357384537211774088820520996004232717.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6357384537211774088820520996004232717.dcm new file mode 100644 index 0000000..1436992 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6357384537211774088820520996004232717.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5de613db003c52c5466a86be487ee46a4cbcfd02b1b0ce7f94f1f3f59b5fbd37 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6441376112498392457505254077668665834.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6441376112498392457505254077668665834.dcm new file mode 100644 index 0000000..5a7a292 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6441376112498392457505254077668665834.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:facebafbe26903f236e27e7f636ed0de3ee0493c4f8cf4af1385a964422b888a +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6597065663458681246337483004965329921.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6597065663458681246337483004965329921.dcm new file mode 100644 index 0000000..a1e4439 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6597065663458681246337483004965329921.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cb09f38ea46821e6e061f1b48e25a689d89634c5ac19c64dcc40f4493614f35 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6658573022075014665744812907032604318.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6658573022075014665744812907032604318.dcm new file mode 100644 index 0000000..82cb658 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6658573022075014665744812907032604318.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:25d0b43607794507378c51991ed1353d31e2974a95651dd58f1d202cca56cd81 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6712840246707125512465521980995994214.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6712840246707125512465521980995994214.dcm new file mode 100644 index 0000000..529308d --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6712840246707125512465521980995994214.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a995f953090267732021e498ba680988a263702572b5809da9e917e73d892ca +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6761223945478082667680320657623117205.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6761223945478082667680320657623117205.dcm new file mode 100644 index 0000000..a52be8f --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6761223945478082667680320657623117205.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8cb910429f12689378d045fa87f8271a6d04c1953c943772807850f15b3271bf +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6835245296550316883645515092656002537.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6835245296550316883645515092656002537.dcm new file mode 100644 index 0000000..c58e4ff --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.6835245296550316883645515092656002537.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fa007f148a1528db6e5d856ad3555fa6bffd7ddc878102d2ac01dd6a6f70c34 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.685137084942333907959889524188759462.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.685137084942333907959889524188759462.dcm new file mode 100644 index 0000000..2508751 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.685137084942333907959889524188759462.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76642af089045c7db2362a6a2e285d3ec5326d2626512183008b121b0b4c1be6 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.69263144930629956310427638087391346.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.69263144930629956310427638087391346.dcm new file mode 100644 index 0000000..dd36748 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.69263144930629956310427638087391346.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2bf346c6ccd51c1bc41d6d82f0741dc396c31f796be825805151ce0e043d52e1 +size 150720 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7027794401230589409829239012624630832.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7027794401230589409829239012624630832.dcm new file mode 100644 index 0000000..694ceba --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7027794401230589409829239012624630832.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86f69e5846122a10454badf1f71fc3bf1eee75a9d804b0570f93728d009cf057 +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7081301029476025717966865993770847007.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7081301029476025717966865993770847007.dcm new file mode 100644 index 0000000..db947af --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7081301029476025717966865993770847007.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c5b6430add643bcf25874e67c69af5d1d0a3f5e217e73c020d3a3920932ac9b4 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7206583323913954541251581408021240329.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7206583323913954541251581408021240329.dcm new file mode 100644 index 0000000..b37d4f2 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7206583323913954541251581408021240329.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c662b0d7128e50c4fcf3a365c65826fdef2373f39548db96d3ebfd1108f5f1e +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7318143735353060334061283529355555291.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7318143735353060334061283529355555291.dcm new file mode 100644 index 0000000..598a992 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7318143735353060334061283529355555291.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3edbce076ca056ca49e1649f2b257d38a6f2f1bb89b46c18a3ea245e908d3001 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.742206910498381871182263929677311012.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.742206910498381871182263929677311012.dcm new file mode 100644 index 0000000..dd1fcca --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.742206910498381871182263929677311012.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3c7f94ec772a5aad4b3a9a1a93a9cdf1408c6417a867b84f4e2a5f635a3275f +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7422432385979430023558602012600868853.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7422432385979430023558602012600868853.dcm new file mode 100644 index 0000000..f4f520f --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7422432385979430023558602012600868853.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:018fd81903aabc173745073668498e0e0092e1d99ff734e88ebf63855772f381 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7460373818674061099498239168987508407.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7460373818674061099498239168987508407.dcm new file mode 100644 index 0000000..8f021b1 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7460373818674061099498239168987508407.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:192e41cd0498fb12c3d9db1e5cfa4beb0d6793bb1d30f2e208ef77faf97b95ae +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7481349549399418277409074537978904253.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7481349549399418277409074537978904253.dcm new file mode 100644 index 0000000..613f133 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7481349549399418277409074537978904253.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0e45008fa866bda4e1b89918007bef6ab32baf50f187abfc498d6a916f83ff9a +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7487959755683702293138632326735878290.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7487959755683702293138632326735878290.dcm new file mode 100644 index 0000000..87642e4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7487959755683702293138632326735878290.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:33626bfcf17ed4dbeef0a1efd2bc7776447e2c7f8745c7878cdcc911e45055d7 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.750956438087847654699077740357585383.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.750956438087847654699077740357585383.dcm new file mode 100644 index 0000000..ba497b4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.750956438087847654699077740357585383.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d7fe9a8c21be464b02f955083b914cdc7aae0e396776ef066dfc132687d524ce +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7631610506895401710001102852770929658.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7631610506895401710001102852770929658.dcm new file mode 100644 index 0000000..5884655 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7631610506895401710001102852770929658.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:065fd9ac8464804136dffafaecae770ccdaa47891cfe3a7b88c059bd4a6c0e74 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7732652048134199842429252293022176383.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7732652048134199842429252293022176383.dcm new file mode 100644 index 0000000..a4882fb --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7732652048134199842429252293022176383.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2ebfc4175618c1c3f539cc82747ee16093d1364f2cbb41e2fb454162e2ccb02 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7781972752181582862799072108849713022.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7781972752181582862799072108849713022.dcm new file mode 100644 index 0000000..44e983e --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7781972752181582862799072108849713022.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1c0f0fccb423470d7e6f06194a593bf4de139c574486183a001dab04481ba59 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7782577618735046667439126660312710220.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7782577618735046667439126660312710220.dcm new file mode 100644 index 0000000..a0102f5 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7782577618735046667439126660312710220.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a0beea2b48b8f3b981d862c15791c6fe44a5a8f2c9fbac7f1ed167886755f31a +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7870401717837385777336241245171044415.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7870401717837385777336241245171044415.dcm new file mode 100644 index 0000000..4085ba4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7870401717837385777336241245171044415.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f45563aac2187b6a0316e431a43713bab8ba1b9d700f94e30b3774814dcb11c1 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7980660904868944201449075257213949689.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7980660904868944201449075257213949689.dcm new file mode 100644 index 0000000..42e969a --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7980660904868944201449075257213949689.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c58772909e57b3862bb6cbbf05f795d5ae4480ce0d8c02f41e3c3d5179c1cc20 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7981154001954224966931165302206197255.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7981154001954224966931165302206197255.dcm new file mode 100644 index 0000000..c4b67c6 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.7981154001954224966931165302206197255.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:984d15b3a7655321fbc75f4e7a3c6cb6878971cbf7fad3fa3ac987107c653b8f +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8049163574043799507014417847828975199.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8049163574043799507014417847828975199.dcm new file mode 100644 index 0000000..ea0b0a8 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8049163574043799507014417847828975199.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dcc04ec374191b412c5f21488416ee6d168737b8b86ff1428a25340027e6cbbc +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8162611483932317619983452423520154636.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8162611483932317619983452423520154636.dcm new file mode 100644 index 0000000..aa592bc --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8162611483932317619983452423520154636.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a8f73b1fc933fe5cafa4e39c98d31a9eac1ae9728c12a718304774c71a2e21b9 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8168150317348920894532963717230388834.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8168150317348920894532963717230388834.dcm new file mode 100644 index 0000000..06cc0de --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8168150317348920894532963717230388834.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0bb32f1dac9eb2ff3d2616f088a34968c8ba7a9af5a97427cebb6a7aba6a4053 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8171430137379649933713011214015618074.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8171430137379649933713011214015618074.dcm new file mode 100644 index 0000000..c9a6378 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8171430137379649933713011214015618074.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1cabef53368a1db5b2e0134e2ce17589aad4fdb1ba7f32f928140bf06cbb273c +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8179892071462079531760347170645287392.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8179892071462079531760347170645287392.dcm new file mode 100644 index 0000000..6a3fadb --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8179892071462079531760347170645287392.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:007cfccbdbc2ba28cd736fdd95243fae150e659a45fa2b537f68c862429d062a +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8237365378403393195438478012205382174.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8237365378403393195438478012205382174.dcm new file mode 100644 index 0000000..5908d3c --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8237365378403393195438478012205382174.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1980d1094919704899ffaae9ca3d84b5eef5122c195ee922351316be42185c4d +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8273438353748895042768359085531460113.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8273438353748895042768359085531460113.dcm new file mode 100644 index 0000000..14d1b62 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8273438353748895042768359085531460113.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:db761c31d168fd8449b1f9f62fb64f654a1dabf7eb6224a71f6716471c76d0e0 +size 150732 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.829681415896452665224477136101765237.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.829681415896452665224477136101765237.dcm new file mode 100644 index 0000000..7f690e0 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.829681415896452665224477136101765237.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eede92e9627fb489d1fa34daec3f7b20403abe81f5a02fd0e56d79918aec25af +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8407381349982180990318229737569507269.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8407381349982180990318229737569507269.dcm new file mode 100644 index 0000000..7ee124f --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8407381349982180990318229737569507269.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eadbe2ab1b2b1e0a6e7c6078e1653df963ed8bc0da956d1c75a0dfc1776c34f6 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8464855210532784225543523074112356205.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8464855210532784225543523074112356205.dcm new file mode 100644 index 0000000..83738ec --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8464855210532784225543523074112356205.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1324ab04daad0e77dadb2cb229c28de0b20fc6f6fb7a82c405f642dcb433aefa +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8525134786162135368787331294760377714.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8525134786162135368787331294760377714.dcm new file mode 100644 index 0000000..c203d54 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8525134786162135368787331294760377714.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d523bce46a034ae77f5de18761ea3e880cc2bc5db3abebe9f3fac63551aa508 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8574549265078372915904470948065785868.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8574549265078372915904470948065785868.dcm new file mode 100644 index 0000000..22aa8a3 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8574549265078372915904470948065785868.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0736515ef25b50b41e3d0176ada8ae0184aa9b347843de28a6ba906b5dba2567 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8575585509648595543554072977940251480.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8575585509648595543554072977940251480.dcm new file mode 100644 index 0000000..f142d02 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8575585509648595543554072977940251480.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ece6ae7d729dc66dc191cfca0ed4466dc484114561ba8c0788214aecb1eb729 +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8621432243665445149839266975414641854.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8621432243665445149839266975414641854.dcm new file mode 100644 index 0000000..8161ea6 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8621432243665445149839266975414641854.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f0cffab09f001ce8121e79a39b78e676433a30446a6e8eb635dba20c42899f29 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8720810616903982173793875470576819469.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8720810616903982173793875470576819469.dcm new file mode 100644 index 0000000..4e18f02 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8720810616903982173793875470576819469.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ad3e66f8032d4092578c3621439e756fbc99c8f5e36792790864a8ff198a380 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8887380356061938605931075302276702327.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8887380356061938605931075302276702327.dcm new file mode 100644 index 0000000..873f023 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8887380356061938605931075302276702327.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f63641a4128969ceeacefd55d78f42b3bad07a624600dca9ed2c8f08f23b2f14 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8940172457999261511869688730520958178.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8940172457999261511869688730520958178.dcm new file mode 100644 index 0000000..acfc8f4 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8940172457999261511869688730520958178.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ba4becc10e0a60dbdd45d02093694452f159b5228e3d7463c2b7df90ecc38b9 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8975838799991772102294406794136089481.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8975838799991772102294406794136089481.dcm new file mode 100644 index 0000000..6bef927 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.8975838799991772102294406794136089481.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:917cdf02bd8359464a7b8eda364fcb5d2a7f67b6bb26099af86d0e3ce6a623d6 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9013674312261915713054516037041527442.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9013674312261915713054516037041527442.dcm new file mode 100644 index 0000000..e1e1aa9 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9013674312261915713054516037041527442.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fc1dac87adc4eb5bde991373fd1a70374070ea0e2e57effc8c1839398e3ed045 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.911407342250437851605538971516132393.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.911407342250437851605538971516132393.dcm new file mode 100644 index 0000000..57a6e58 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.911407342250437851605538971516132393.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d805e91cc5c92ceeb11289293095c8410d1564a12355416623de1a6bf3cd146 +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9312885525839692115388851990352305547.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9312885525839692115388851990352305547.dcm new file mode 100644 index 0000000..074ba36 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9312885525839692115388851990352305547.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6793f5e25815dbcffe564042c59d57a0d6328745a58afbf3a9280bfe033ad1c +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9586854040391990792601446166314705903.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9586854040391990792601446166314705903.dcm new file mode 100644 index 0000000..ccea30d --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9586854040391990792601446166314705903.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ba8b670b07f7b7bc9aa98769ff17bdf3d8c8a19c3dd6bfd3dff3c136ac7580a +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9596694057118048084660713935839816582.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9596694057118048084660713935839816582.dcm new file mode 100644 index 0000000..9220fbe --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9596694057118048084660713935839816582.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3fb288d4a983a97dd16fe4df76869d008740edd98214820c936f497efdb54d50 +size 150730 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9615381644600228736239254996226957517.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9615381644600228736239254996226957517.dcm new file mode 100644 index 0000000..a59ea26 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9615381644600228736239254996226957517.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ca4a0da3aea0373314bd924e9dbd081acaeaacb3eb7fa8937083093c8a7cb6f +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9699019752073144820794159416056179223.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9699019752073144820794159416056179223.dcm new file mode 100644 index 0000000..a5ca99b --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9699019752073144820794159416056179223.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:998ee724e860e9101144ce683c82eb59ab8d91a8944f466b68d4a7e59da5b1b7 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9701334368700607879183255977802156769.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9701334368700607879183255977802156769.dcm new file mode 100644 index 0000000..02d4724 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9701334368700607879183255977802156769.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7b18ab160d275792433d2e172e9e46d551889d0b7f7c43113291541f62d368d2 +size 150726 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.989578340813719233003086565949201853.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.989578340813719233003086565949201853.dcm new file mode 100644 index 0000000..0529403 --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.989578340813719233003086565949201853.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:126a296ceecf1ac9a3ae3f86deb65b56eff9a1b452d4cd84ac996aa3b0da370b +size 150724 diff --git a/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9927869533253832950062842559108355546.dcm b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9927869533253832950062842559108355546.dcm new file mode 100644 index 0000000..8b9c8ea --- /dev/null +++ b/Images/LargeSeriesWithContour/P4_HD-CT-1.2.826.0.1.3680043.2.1143.9927869533253832950062842559108355546.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:196f10f201acffe6a6dc7b30441c826e02e793fa175ddc7c6925f2d2bd989077 +size 150732 diff --git a/Images/LargeSeriesWithContour/rtstruct.dcm b/Images/LargeSeriesWithContour/rtstruct.dcm new file mode 100644 index 0000000..2d943ac --- /dev/null +++ b/Images/LargeSeriesWithContour/rtstruct.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:684dd175321e578fd850f228debfbb1754a6bc66797f7f6e3f2b7bc4034ac256 +size 2048160 diff --git a/Images/MR/1.dcm b/Images/MR/1.dcm new file mode 100644 index 0000000..d6e9ffa --- /dev/null +++ b/Images/MR/1.dcm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:217e9cd2347a4369bd5c9c2b0b2968e807454609e5a27a5c175d585c153e7669 +size 526980 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9e841e7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/README.md b/README.md new file mode 100644 index 0000000..83e9d94 --- /dev/null +++ b/README.md @@ -0,0 +1,1289 @@ +# InnerEye-Gateway + +## Overview + +This document has instructions specially for InnerEye-Gateway [https://github.com/microsoft/InnerEye-Gateway](https://github.com/microsoft/InnerEye-Gateway). + +*For InnerEye-Inference repo please visit* [*this*](https://github.com/microsoft/InnerEye-Inference) *documentation.* + +The InnerEye-Gateway comprises Windows services that act as a DICOM Service Class Provider. After an Association Request to C-STORE a set of DICOM image files, these will be anonymised and passed to a web service running [InnerEye-Inference](https://github.com/microsoft/InnerEye-Inference). Inference will then pass them to an instance of [InnerEye-Deeplearning](https://github.com/microsoft/InnerEye-DeepLearning) running on Azure to execute InnerEye-DeepLearning models. The result is downloaded, de-anonymised and passed to a configurable DICOM destination. All DICOM image files, and the model output, are automatically deleted immediately after use. + +The gateway should be installed on a machine within your DICOM network that is able to access a running instance of [InnerEye-Inference](https://github.com/microsoft/InnerEye-Inference). + +![environment](./Docs/environment.png) + +## Contents + + + +- [Overview](#overview) +- [Contents](#contents) +- [Getting Started](#getting-started) +- [License Keys](#license-keys) +- [To Build The Gateway](#to-build-the-gateway) +- [To Run The Tests](#to-run-the-tests) +- [To Run The Gateway](#to-run-the-gateway) +- [Architecture](#architecture) + - [Receiver Application](#receiver-application) + - [Processor Application](#processor-application) + - [Upload Service](#upload-service) + - [Download Service](#download-service) + - [Push Service](#push-service) + - [Delete Service](#delete-service) +- [Anonymisation](#anonymisation) +- [Configuration](#configuration) + - [Common Configuration](#common-configuration) + - [Processor Configuration](#processor-configuration) + - [Receiver Configuration](#receiver-configuration) + - [Model Configuration](#model-configuration) + - [ModelsConfig](#modelsconfig) + - [ChannelConstraint](#channelconstraint) + - [DicomConstraint](#dicomconstraint) + - [GroupConstraint](#groupconstraint) + - [RequiredTagConstraint](#requiredtagconstraint) + - [DicomTagConstraint](#dicomtagconstraint) + - [GroupTagConstraint](#grouptagconstraint) + - [StringContainsConstraint](#stringcontainsconstraint) + - [RegexConstraint](#regexconstraint) + - [OrderedDateTimeConstraint, OrderedDoubleConstraint, OrderedIntConstraint, OrderedStringConstraint](#ordereddatetimeconstraint-ordereddoubleconstraint-orderedintconstraint-orderedstringconstraint) + - [UIDStringOrderConstraint](#uidstringorderconstraint) + - [TimeOrderConstraint](#timeorderconstraint) +- [Licensing](#licensing) +- [Contributing](#contributing) +- [Microsoft Open Source Code of Conduct](#microsoft-open-source-code-of-conduct) + + + +## Getting Started + +To get started with setting up this project you will need the following pre-requisites: + +1. A machine running Windows 10, because the Gateway runs as Windows Services. It requires a network card to act as a DICOM SCP and for access to the InnerEye-Inference web service. A large hard disk is not required because the DICOM image files are not kept for very long. + +1. Visual Studio 2019 Community Edition [(Download Visual Studio Community Edition)](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=16) + +1. .Net Framework 4.6.2 [Download .Net Framework](https://dotnet.microsoft.com/download/dotnet-framework/thank-you/net462-developer-pack-offline-installer) + +1. Run the PowerShell script to download two DICOM tools required for testing the gateway: [./Source/Microsoft.Gateway/download_dcmtk.ps1](./Source/Microsoft.Gateway/download_dcmtk.ps1). Note that these tools are only required for testing but the test projects and therefore the gateway will not build without them. Note also that the default PowerShell execution policy will not allow running scripts, more information is available here: [about execution policies](https:/go.microsoft.com/fwlink/?LinkID=135170). In brief: + + a. Start PowerShell with the `Run as administrator` option. + + b. Run the PowerShell command: + + ```shell + Set-ExecutionPolicy -ExecutionPolicy Unrestricted + ``` + + and agree to the change. + +1. InnerEye-Inference service from [https://github.com/microsoft/InnerEye-Inference](https://github.com/microsoft/InnerEye-Inference) running as a web service, using, for example [Azure Web Services](https://azure.microsoft.com/en-gb/services/app-service/web/). Note the URI that the service has been deployed to and the license key stored in the environment variable `CUSTOMCONNSTR_API_AUTH_SECRET` on the InnerEye-Inference deployment, they are needed as explained below. + +## License Keys + +For security in InnerEye-Gateway and InnerEye-Inference the license keys are stored in environment variables and never stored in JSON or other configuration files. InnerEye-Inference uses the environment variable `CUSTOMCONNSTR_API_AUTH_SECRET` whereas InnerEye-Gateway allows the name of the environment variable to be configured in the JSON file in the `LicenseKeyEnvVar` property. This is so that the tests may be configured to run against a different InnerEye-Inference web service. + +Alongside `LicenseKeyEnvVar` the property `InferenceUri` holds the URI of a running instance of InnerEye-Inference and the environment variable identified by `LicenseKeyEnvVar` should hold the license key for that instance. + +For example if `InferenceUri` is "https://myinnereyeinference.azurewebsites.net", `LicenseKeyEnvVar` is "MY_GATEWAY_API_AUTH_SECRET", and the contents of the environment variable `CUSTOMCONNSTR_API_AUTH_SECRET` used for "https://myinnereyeinference.azurewebsites.net" is `MYINFERENCELICENSEKEY` then set this environment variable with the PowerShell command: + +```cmd +setx MY_GATEWAY_API_AUTH_SECRET MYINFERENCELICENSEKEY +``` + +## To Build The Gateway + +1. Clone the repository. + +1. Open the project solution file [./Source/Microsoft.Gateway/Microsoft.Gateway.sln](./Source/Microsoft.Gateway/Microsoft.Gateway.sln) in Visual Studio 2019. + +1. Set the project platform to **x64**. + +1. Build the Solution. + +## To Run The Tests + +1. Download the DICOM tools using the PowerShell script in [Getting Started](#getting-started). + +1. For live tests check the configuration settings for `Microsoft.InnerEye.Listener.Processor` in [./Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayProcessorConfig.json](./Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayProcessorConfig.json). Check the following settings in `ProcessorSettings`: + + - `InferenceUri` is the Uri for the InnerEye-Inference web service from [Getting Started](#getting-started). See [License Keys](#license-keys) for more details. + + - `LicenseKeyEnvVar` is the name of the environment variable which contains the license key for the InnerEye-Inference web service at `InferenceUri`. See [License Keys](#license-keys) for more details. + +1. Make sure the testing **Default Processor Architecture** is set to **x64**. This can be checked by navigating to **Test > Test Settings > Default Process Architecture** + +1. The tests can be executed from Visual Studio 2019 by navigating to **Test > Windows > Test Explorer**. + +1. All the available tests will be visible in the Test Explorer. + +1. A test can be executed by right clicking on the test and selecting the Run Selected Tests option. + +1. To execute all the available tests, click on the Run All link in the Test Explorer. + +1. The log for the test execution can be found in the Output window. + +## To Run The Gateway + +1. The gateway uses multiple startup projects: `Microsoft.InnerEye.Listener.Processor` and `Microsoft.InnerEye.Listener.Receiver`. Configure this in Visual Studio by right clicking on the Solution in Solution Explorer and selecting "Set Startup Projects...". Click the radio button "Multiple startup projects" and for the projects above select "Start" in the "Action" combo box. + +1. Check the configuration settings for `Microsoft.InnerEye.Listener.Processor` in [./Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json](./Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json). More details are in [Processor Configuration](#processor-configuration), but the most important parameters to check are in `ProcessorSettings`: + + - `InferenceUri` is the Uri for the InnerEye-Inference web service from [Getting Started](#getting-started). See [License Keys](#license-keys) for more details. + + - `LicenseKeyEnvVar` is the name of the environment variable which contains the license key for the InnerEye-Inference web service at `InferenceUri`. See [License Keys](#license-keys) for more details. + +1. Check the configuration settings for `Microsoft.InnerEye.Listener.Receiver` in [./Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json](./Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json). More details are in [Receiver Configuration](#receiver-configuration), but the most important parameter to check is: + + - `Port` in `GatewayDicomEndPoint` in `ReceiveServiceConfig` - holds the IP port the receiver service listens on. + +1. Check the configuration settings for the application entity models in the folder [./Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig](./Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig). The configuration may be split across multiple JSON files, if desired, and the configuration will be merged. The structure of these files is that each must contain an array of the form: + +```json +[ + { + "CallingAET": << Calling application entity title >>, + "CalledAET": << Called application entity title >>, + "AETConfig": { + "Config": { + "AETConfigType": << One of "Model", "ModelDryRun", or "ModelWithResultDryRun" >> + "ModelsConfig": [ + { + "ModelId": << Model id for model in inference service, e.g. "PassThroughModel:3" >>, + "ChannelConstraints": [ << Array of channel constraints >> ], + "TagReplacements": [ << Array of tag replacements >> ] + }, + ... + ] + }, + "Destination": << Destination >>, + "ShouldReturnImage": << Return image flag >> + } + } +``` + +All JSON files in this folder are loaded and parsed. If the same `CallingAET` and `CalledAET` are found in more than one instance then the `ModelsConfig` arrays are concatenated to create one instance sharing all the other properties (which are taken from the first instance found). More details are in [Model Configuration](#model-configuration). + +## Architecture + +The gateway runs as two applications or services configured with [JSON](https://en.wikipedia.org/wiki/JSON) files. The two applications communicate, and operate internally, using [message queues](https://en.wikipedia.org/wiki/Message_queue) built on [Sqlite](https://sqlite.org/index.html). + +![Sequence](./Docs/sequence.png) + +To edit this, see: [Rebuild Diagram](./Diagram.md). + +### Receiver Application + +The first application is `Microsoft.InnerEye.Listener.Receiver`. This create a DICOM server that listens on an IP port for incoming [DICOM](https://en.wikipedia.org/wiki/DICOM) communications and stores requests that are accepted on a message queue. This is configured in [Receiver Configuration](#receiver-configuration). + +In detail, this: + +1. Starts a DICOM server that listens for incoming DICOM messages. + +1. On a DICOM Association Request, checks against the configured acceptable SOP classes and their transfer syntaxes. + +1. On a DICOM C-Store Request, stores the incoming DICOM image file to a subfolder of the RootDicomFolder. There will be one of these for each image slice. + +1. On a DICOM Association Release Request, puts a new message on the **Upload** message queue with details of the DICOM Association and location of the files. + +### Processor Application + +The second application is `Microsoft.InnerEye.Listener.Processor`. This waits for messages on the **Upload** message queue from the [Receiver Application](#receiver-application). When a new message is received it: copies and anonymises the received DICOM images, sends them to the InnerEye-Inference web service, waits and then downloads the resulting DICOM-RT file. This is then de-anonymised and sent on to the configured destination. + +This is configured in [Processor Configuration](#processor-configuration). + +In detail, this starts 4 worker tasks: the [Delete Service](#delete-service), [Download Service](#download-service), [Push Service](#push-service), and [Upload Service](#upload-service), which communicate using the message queues: **Delete**, **Download**, **Push**, and **Upload**. + +#### Upload Service + +This service watches the **Upload** message queue for messages from the [Receiver](#receiver) application that indicate a DICOM request has completed. When a new message is received it: + +1. Tries to find an application entity model configured with the matching `CalledAET` and `CallingAET` ([Model Configuration](#model-configuration)). + +1. If there is a corresponding model and it is set to `Model` or `ModelWithResultDryRun`: + + a. Reads the received DICOM image files from the subfolder of the RootDicomFolder. + + b. Groups the DICOM image files by Study Instance UID, and then grouped by Series Instance UID. + + c. Compares each group with each channel config until a set of filtered DICOM image files matches the constraints in the channel config. + + d. Copies the image data and only the required DICOM tags to a set of new images, anonymises the remaining DICOM tags. + + e. Zips the images and POSTs them to the InnerEye-Inference web service. + + f. If the model `ShouldReturnImage` is set then copies all sent data to either the Results or DryRunModelWithResultFolder subfolders of the RootDicomFolder. + + g. Creates a new message on the **Download** queue for this web service request. + + h. Creates a new message on the **Delete** queue to delete the received DICOM image files. + +1. If there is a corresponding model and it is set to `ModelDryRun`: + + a. Reads the received DICOM image files from the subfolder of the RootDicomFolder. + + b. Anonymises the DICOM tags. + + c. Saves the anonymised image files to the DryRunModelAnonymizedImage subfolder of the RootDicomFolder. + + d. Creates a new message on the **Delete** queue to delete the received image files. + +1. If there is no correspond model then: + + a. Creates a new message on the **Delete** queue to delete the received image files. + +#### Download Service + +This service watches the **Download** message queue for messages from the [Upload Service](#upload-service) that indicate a set of DICOM files have been sent to the InnerEye-Inference web service. When a new message is received it: + +1. Waits for the InnerEye-Inference web service to perform segmentation then downloads the resulting DICOM-RT file. + +1. De-anonymises the DICOM-RT file using data from the original received DICOM image files. + +1. Saves the de-anonymised file to either the Results or DryRunModelWithResultFolder subfolders of the RootDicomFolder. + +1. If this is not `ModelWithResultDryRun` then creates a new message on the **Push** message queue. + +#### Push Service + +This service watches the **Push** message queue for messages from the [Download Service](#download-service) that indicate that a DICOM-RT file has been downloaded from the InnerEye-Inference web service. When a new message is received it: + +1. Tries to find an application entity model configured with the matching `CalledAET` and `CallingAET` ([Model Configuration](#model-configuration)). + +1. Reads the de-anonymised DICOM-RT file from the Results subfolder of the RootDicomFolder. + +1. Sends them to the DICOM Destination as set in the application entity model. + +1. Creates a new message on the **Delete** queue to delete the received DICOM-RT file. + +#### Delete Service + +This service watches the **Delete** message queue for messages from the other services. If it receives a message it will delete the specified files or folders. + +## Anonymisation + +The process for handling DICOM files is: + +1. The [Receiver Application](#receiver-application) saves the incoming DICOM files directly to a subfolder of RootDicomFolder and passes a message to the [Upload Service](#upload-service). + +1. The [Upload Service](#upload-service) loads the DICOM files and makes a copy of each file in memory (discarding the image data), as `reference images` for de-anonymisation later. The following tags are kept, along with the tags mentioned below: + +```c# +// Patient module +PatientID, +PatientName, +PatientBirthDate, +PatientSex, + +// Study module +StudyDate, +StudyTime, +ReferringPhysicianName, +StudyID, +AccessionNumber, +StudyDescription +``` + +3. The [Upload Service](#upload-service) loads the DICOM files again and makes a second copy of each file in memory, performing the following transformations to the DICOM tags: + +a. The following tags are kept unchanged: + +```c# +// Geometry +PatientPosition, +Columns, +Rows, +PixelSpacing, +ImagePositionPatient, +ImageOrientationPatient, +SliceLocation, +BodyPartExamined, + +// Modality +Modality, +ModalityLUTSequence, +HighBit, +BitsStored, +BitsAllocated, +SamplesPerPixel, +PixelData, +PhotometricInterpretation, +PixelRepresentation, +RescaleIntercept, +RescaleSlope, +ImageType, + +// UIDs +SOPClassUID, + +// RT +// RT DicomFrameOfReference +RTReferencedStudySequence, + +// RT DicomRTContour +ReferencedROINumber, +ROIDisplayColor, +ContourSequence, +ROIContourSequence, + +// RT DicomRTContourImageItem +ReferencedSOPClassUID, + +// RT DicomRTContourItem +NumberOfContourPoints, +ContourData, +ContourGeometricType, +ContourImageSequence, + +// RT DicomRTObservation +RTROIObservationsSequence, +ObservationNumber, + +// RT DicomRTReferencedStudy +RTReferencedSeriesSequence, + +// RT DicomRTStructureSet +ReferencedFrameOfReferenceSequence, + +// RT DicomRTStructureSetROI +ROINumber, +ROIName, +ROIGenerationAlgorithm, +StructureSetROISequence, +``` + +b. The following DICOM tags are replaced with a hash of their value: + +```C# +// UIDs +PatientID, +SeriesInstanceUID, +StudyInstanceUID, +SOPInstanceUID, + +// RT +// RT DicomFrameOfReference +FrameOfReferenceUID, + +// RT DicomRTContourImageItem +ReferencedSOPInstanceUID, + +// RT DicomRTStructureSet +StructureSetLabel, +StructureSetName, + +// RT DicomRTStructureSetROI +ReferencedFrameOfReferenceUID +``` + +c. All other tags are discarded. + +d. The transformed DICOM image files are then zipped before sending to the InnerEye-Inference web service. + +4. After the [Download Service](#download-service) downloads the DICOM-RT file it performs the following de-anonymisation transformations to the DICOM-RT file: + +a. The first set of tags + +```c# +PatientID, +... +StudyDescription +``` + +are copied from the first `reference image`. + +b. All the tags that have been hashed are replaced with tags from the `reference images`. + +c. Model configured tag transformations are applied, to either replace a tag entirely or append some fixed text. + +## Configuration + +### Common Configuration + +Both applications share some common configuration. + +The InnerEye-Gateway repeatedly checks if its configuration files have been modified, to ensure that it can be updated without interrupting the DICOM processing. This is configured with `ConfigurationServiceConfig`. The algorithm behind this is that each `ConfigurationRefreshDelaySeconds` the application will check connectivity to the InnerEye-Inference service (for the Processor service) and check for changes to the configuration files. If `ConfigCreationDateTime` is different to the last loaded configuration file and `ApplyConfigDateTime` is past then the application will effectively restart with the new configuration. + +```json +{ + "ServiceSettings": { + "RunAsConsole": boolean + }, + << service specific configuration >>, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": date/time, + "ApplyConfigDateTime": date/time, + "ConfigurationRefreshDelaySeconds": number + } +} +``` + +For example: + +```json +{ + "ServiceSettings": { + "RunAsConsole": true + }, + << service specific configuration >>, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2020-05-31T20:14:51", + "ApplyConfigDateTime": "2020-05-31T20:14:51", + "ConfigurationRefreshDelaySeconds": 60 + } +} +``` + +Where: + +- `RunAsConsole` if true means that this application runs as a normal console application, otherwise it runs as a windows service. This can be used for debugging or testing. + +- `ConfigCreationDateTime` is the date and time that this configuration was created. It does not need to be exact, because it is only used to identify when the file has been edited. + +- `ApplyConfigDateTime` is the date and time that this configuration should be applied. + +- `ConfigurationRefreshDelaySeconds` is the time in seconds the application will wait between checks if a new configuration file is available. + +### Processor Configuration + +The processor application is configured by [./Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json](./Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json). + +The structure of this configuration file is: + +```json +{ + "ServiceSettings": { + "RunAsConsole": boolean + }, + "ProcessorSettings": { + "LicenseKeyEnvVar": string, + "InferenceUri": string + }, + "DequeueServiceConfig": { + "MaximumQueueMessageAgeSeconds": number, + "DeadLetterMoveFrequencySeconds": number + }, + "DownloadServiceConfig": { + "DownloadRetryTimespanInSeconds": number, + "DownloadWaitTimeoutInSeconds": number + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": date/time, + "ApplyConfigDateTime": date/time, + "ConfigurationRefreshDelaySeconds": number + } +} +``` + +For example: + +```json +{ + "ServiceSettings": { + "RunAsConsole": true + }, + "ProcessorSettings": { + "LicenseKeyEnvVar": "MYINFERENCELICENSEKEY", + "InferenceUri": "https://myinnereyeinference.azurewebsites.net" + }, + "DequeueServiceConfig": { + "MaximumQueueMessageAgeSeconds": 100, + "DeadLetterMoveFrequencySeconds": 1 + }, + "DownloadServiceConfig": { + "DownloadRetryTimespanInSeconds": 5, + "DownloadWaitTimeoutInSeconds": 3600 + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2020-05-31T20:14:51", + "ApplyConfigDateTime": "2020-05-31T20:14:51", + "ConfigurationRefreshDelaySeconds": 60 + } +} +``` + +Where: + +- `ServiceSettings` and `ConfigurationServiceConfig` are as [above](#common-configuration). + +- `InferenceUri` is the Uri for the InnerEye-Inference web service from [Getting Started](#getting-started). See [License Keys](#license-keys) for more details. + +- `LicenseKeyEnvVar` is the name of the environment variable which contains the license key for the InnerEye-Inference web service at `InferenceUri`. See [License Keys](#license-keys) for more details. + +- `MaximumQueueMessageAgeSeconds` is an internal message queue setting - the maximum time in seconds a message may spend in a queue before being automatically deleted. + +- `DeadLetterMoveFrequencySeconds` is another internal message queue setting - if there is an error processing a message then it will be moved to a [Dead letter queue](https://en.wikipedia.org/wiki/Dead_letter_queue) and this is the time in seconds before moving messages back from the dead letter queue to the normal queue. + +- `DownloadRetryTimespanInSeconds` is the delay in seconds between attempts to download the completed segmentation from the InnerEye-Inference service. + +- `DownloadWaitTimeoutInSeconds` is the maximum time in seconds to wait whilst attempting to download the completed segmentation. + +### Receiver Configuration + +The receiver application is configured by [./Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json](./Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json) and also by model configuration files in the folder [./Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig](./Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig). The model configuration is explained below: [Model Configuration](#model-configuration). + +The structure of GatewayReceiveConfig.json configuration file is: + +```json +{ + "ServiceSettings": { + "RunAsConsole": boolean + }, + "ReceiveServiceConfig": { + "GatewayDicomEndPoint": { + "Title": string, + "Port": number, + "Ip": string + }, + "RootDicomFolder": string, + "AcceptedSopClassesAndTransferSyntaxesUIDs": object of the form { + string: array of strings, + string: array of strings, + string: array of strings, + ... + } + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": date/time, + "ApplyConfigDateTime": date/time, + "ConfigurationRefreshDelaySeconds": number + } +} +``` + +For example: + +```json +{ + "ServiceSettings": { + "RunAsConsole": true + }, + "ReceiveServiceConfig": { + "GatewayDicomEndPoint": { + "Title": "GATEWAY", + "Port": 111, + "Ip": "localhost" + }, + "RootDicomFolder": "C:\\InnerEyeGateway\\", + "AcceptedSopClassesAndTransferSyntaxesUIDs": { + "1.2.840.10008.1.1": [ "1.2.840.10008.1.2.1", "1.2.840.10008.1.2" ], + "1.2.840.10008.5.1.4.1.1.481.3": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1" ], + "1.2.840.10008.5.1.4.1.1.2": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.5" ], + "1.2.840.10008.5.1.4.1.1.4": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.5" ] + } + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2018-07-25T20:14:51.539351Z", + "ApplyConfigDateTime": "2018-07-25T20:14:51.539351Z", + "ConfigurationRefreshDelaySeconds": 60 + } +} +``` + +Where: + +- `ServiceSettings` and `ConfigurationServiceConfig` are as [above](#common-configuration). + +- `ReceiveServiceConfig.GatewayDicomEndPoint.Title` is only used for testing, but must be supplied. + +- `ReceiveServiceConfig.GatewayDicomEndPoint.Port` is the IP port the DICOM server will listen for DICOM messages on. + +- `ReceiveServiceConfig.GatewayDicomEndPoint.Ip` is also only used for testing, but must be supplied. + +- `RootDicomFolder` is the folder to be used for temporarily storing DICOM files. + +- `AcceptedSopClassesAndTransferSyntaxesUIDs` is a dictionary of [DICOM Service-Object Pair (SOP) Classes](http://dicom.nema.org/dicom/2013/output/chtml/part06/chapter_A.html) and [Transfer Syntax UIDs](http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_10.html) that the application supports. + Here: + - "1.2.840.10008.1.1" is the Verification SOP Class + - "1.2.840.10008.5.1.4.1.1.481.3" is Radiation Therapy Structure Set Storage. + - "1.2.840.10008.5.1.4.1.1.2" is CT Image Storage + - "1.2.840.10008.5.1.4.1.1.4" is MR Image Storage + + For each of these SOPs there is a list of supported Transfer Syntax UIDs. For example: + - "1.2.840.10008.1.2" is Implicit VR Endian: Default Transfer Syntax for DICOM. + - "1.2.840.10008.1.2.4.57" is a type of JPEG. + +### Model Configuration + +The application entity models are configured in the folder [./Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig](./Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig). The configuration may be split across multiple JSON files, if desired, and the configuration will be merged. The structure of these files is that each must contain an array of the form: + +```json +[ array of application entity model config objects of the form: + { + "CallingAET": string, + "CalledAET": string, + "AETConfig": { + "Config": { + "AETConfigType": string, one of "Model", "ModelDryRun", or "ModelWithResultDryRun" + "ModelsConfig": [ array of models config objects of the form: + { + "ModelId": string, + "ChannelConstraints": [ array of channel constraints objects ], + "TagReplacements": [ array of tag replacements objects ] + } + ] + }, + "Destination": { + "Title": string, + "Port": number, + "Ip": string + }, + "ShouldReturnImage": boolean + } + } +] +``` + +For example: + +```json +[ + { + "CallingAET": "RADIOMICS_APP", + "CalledAET": "PassThroughModel", + "AETConfig": { + "Config": { + "AETConfigType": "Model", + "ModelsConfig": [ + { + "ModelId": "PassThroughModel:3", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [ + { + "RequirementLevel": "PresentNotEmpty", + "Constraint": { + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + }, + "discriminator": "UIDStringOrderConstraint" + }, + "discriminator": "RequiredTagConstraint" + } + ], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [ + { + "RequirementLevel": "PresentNotEmpty", + "Constraint": { + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + }, + "discriminator": "UIDStringOrderConstraint" + }, + "discriminator": "RequiredTagConstraint" + } + ], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": 50, + "MaxChannelImages": 1000 + } + ], + "TagReplacements": [ + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 2 + }, + "Value": "InnerEye" + }, + { + "Operation": "AppendIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 38 + }, + "Value": " NOT FOR CLINICAL USE" + } + ] + } + ] + }, + "Destination": { + "Title": "RADIOMICS_APP", + "Port": 104, + "Ip": "127.0.0.1" + }, + "ShouldReturnImage": false + } + } +] +``` + +Where: + +- `CallingAET` is the calling application entity title to be matched. + +- `CalledAET` is the called application entity title to be matched. + +- `AETConfig` consists of three parts: + + - `Config` consists of the pair: + + - `AETConfigType` is one of "Model", "ModelDryRun", or "ModelWithResultDryRun". "Model" is the normal case, the other two are for debugging. "ModelDryRun" means that the received DICOM image files will be anonymised and saved to the DryRunModelAnonymizedImage subfolder of RootDicomFolder. "ModelWithResultDryRun" means almost the same as "Model" except that the DICOM-RT file is downloaded to the DryRunRTResultDeAnonymized subfolder of RootDicomFolder and it is not pushed to a DICOM destination. + + - `ModelsConfig` is an array of [ModelsConfig](#modelsconfig) objects, described below. + + - `Destination` is where to send the resulting DICOM-RT file, consisting of: + + - `Title` is the destination application entity title, + + - `Port` is the destination application entity port, + + - `Ip` is the destination IP addres. + + - `ShouldReturnImage` is true if the original received DICOM image files should be returned when InnerEye-Inference service is complete, false otherwise. + +#### ModelsConfig + +Each model config has the following structure: + +```json +{ + "ModelId": string, + "ChannelConstraints": [ array of objects of the form + { + "ChannelID": string, + "ImageFilter": { + "Constraints": [ array of constraints ], + "Op": string, one of "And" or "Or", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [ array of constraints ], + "Op": string, one of "And" or "Or", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": number, + "MaxChannelImages": number + } + ], + "TagReplacements": [ array of objects of the form: + { + "Operation": string, one of "UpdateIfExists" or "AppendIfExists", + "DicomTagIndex": { + "Group": number, + "Element": number + }, + "Value": string + } + ] +} +``` + +For example: + +```json +{ + "ModelId": "PassThroughModel:3", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [ + { + "RequirementLevel": "PresentNotEmpty", + "Constraint": { + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + }, + "discriminator": "UIDStringOrderConstraint" + }, + "discriminator": "RequiredTagConstraint" + } + ], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [ + { + "RequirementLevel": "PresentNotEmpty", + "Constraint": { + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + }, + "discriminator": "UIDStringOrderConstraint" + }, + "discriminator": "RequiredTagConstraint" + } + ], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": 50, + "MaxChannelImages": 1000 + } + ], + "TagReplacements": [ + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 2 + }, + "Value": "InnerEye" + }, + { + "Operation": "AppendIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 38 + }, + "Value": " NOT FOR CLINICAL USE" + } + ] +} +``` + +Where: + +- `ModelId` is the model identifier that is passed to the InnerEye-Inference service. + +- `ChannelConstraints` is an array of [ChannelConstraint](#channelconstraint) that are applied to the received DICOM image files. The algorithm here is that: + + - The image files are first grouped by Study Instance UID (which must exist), and then grouped by Series Instance UID (which must also exist) + + - For each group of shared Study and Series Instance UIDs: the channel constraints for each `ModelsConfig` are applied in order to the image group. If there is a match then the matching ModelsConfig and image group are then used. The [channel constraints](#channelconstraints) are explained below. + +- `TagReplacements` is a list of DICOM tag replacements that are performed for DICOM-RT file de-anonymisation. The algorithm here is during de-anonymisation to work through all the `TagReplacements`: + + - If the file contains the tag as specified in `DicomTagIndex`: + + - If the `Operation` is "UpdateIfExists" then replace the existing tag with `Value`; + + - If the `Operation` is "AppendIfExists" then append `Value` to the existing tag. + +#### ChannelConstraint + +Each channel constraint has the form: + +```json +{ + "ChannelID": string, + "ImageFilter": { + "Constraints": [ array of constraints ], + "Op": string, one of "And" or "Or", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [ array of constraints ], + "Op": string, one of "And" or "Or", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": number, + "MaxChannelImages": number +} +``` + +Where: + +- `ChannelID` is the id of the channel. It is used in the zip file sent to the InnerEye-Inference service. + +- `ImageFilter` is a [GroupConstraint](#groupconstraint) used to filter the DICOM image files before applying the constraints. This selects a subset of images from the same series by filtering unwanted data e.g. extraneous sop classes. + +- `ChannelConstraints` is a [GroupConstraint](#groupconstraint) used on the images that have passed through `ImageFilter`. + +- `MinChannelImages` is the minimum number of files required for this channel. Use 0 or less to impose no constraint. This is the inclusive lower bound for the number of filtered images. + +- `MaxChannelImages` is the maximum number of files allowed for this channel. Use 0 or less to impose no maximum constraint. This is the inclusive upper bound for the number of filtered images. + +### DicomConstraint + +There are many types of DicomConstraint. So they can be identified when loading the JSON they are all of the form: + +```json +{ + << constraint specific data >>, + "discriminator": string +} +``` + +Where: + +- `discriminator` identifies the DicomConstraint type. + +These are split into two groups. The first group contains [GroupConstraint](#groupconstraint) and [RequiredTagConstraint](#requiredtagconstraint) and are sort of container objects. The second group are all instances of [DicomTagConstraint](#dicomtagconstraint) which specify a DICOM tag and a constraint to be applied. + +#### GroupConstraint + +This constraint acts as a container for a set of other constraints that must either all pass, or at least one of them must pass. + +```json +{ + "Constraints": [ array of constraints ], + "Op": string, one of "And" or "Or", + "discriminator": "GroupConstraint" +} +``` + +Where: + +- `Constraints` is an array of [DicomConstraint](dicomconstraint). + +- `Op` controls whether all constraints ("And") must be met, or at least one of them ("Or"). + +- `discriminator` is as [DicomConstraint](#dicomconstraint). + +#### RequiredTagConstraint + +This constraint acts as a container for a tag constraint and a requirement on the tag. + +```json +{ + "RequirementLevel": string, one of "PresentNotEmpty", "PresentCanBeEmpty", or "Optional", + "Constraint": a dicom tag constraint object, + "discriminator": "RequiredTagConstraint" +} +``` + +For example: + +```json +{ + "RequirementLevel": "PresentNotEmpty", + "Constraint": { + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + }, + "discriminator": "UIDStringOrderConstraint" + }, + "discriminator": "RequiredTagConstraint" +} +``` + +Where: + +- `RequirementLevel` means: + + - "PresentNotEmpty" - the tag must be present and the conditions on the tag must pass. + + - "PresentCanBeEmpty" - the tag must be present and the conditions must pass when the tag is non-empty. + + - "Optional" - the tag does not need to be present but the condition must pass if the tag is present and non-empty. + +- `Constraint` is any [DicomTagConstraint](#dicomtagconstraint). + +- `discriminator` is as [DicomConstraint](#dicomconstraint). + +### DicomTagConstraint + +This group of DICOM constraints operate on a DICOM tag. They are all of the form: + +```json +{ + << tag constraint specific data >>, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": string +} +``` + +Where: + +- `Index` identifies the target DICOM tag. + +- `discriminator` is as [DicomConstraint](dicomconstraint). + +#### GroupTagConstraint + +This acts as a container for a set of constraints and a DICOM tag that the constraints apply to. + +```json +{ + "Group": group constraint object, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": "GroupTagConstraint" +} +``` + +Where: + +- `Group` is a [GroupConstraint](#groupconstraint) + +- `Index` is as [DicomTagConstraint](#dicomtagconstraint). + +- `discriminator` is as [DicomConstraint](dicomconstraint). + +#### StringContainsConstraint + +This constraint tests that a DICOM tag contains a given value. + +```json +{ + "Match": string, + "Ordinal": number, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": "StringContainsConstraint" +} +``` + +For example: + +```json +{ + "Match": "AXIAL", + "Ordinal": -1, + "Index": { + "Group": 8, + "Element": 8 + }, + "discriminator": "StringContainsConstraint" +} +``` + +Where: + +- `Match` is the string that the DICOM tag should contain. + +- `Ordinal` is the ordinal to extract from the tag or -1 to extract all. + +- `Index` is as [DicomTagConstraint](#dicomtagconstraint). + +- `discriminator` is as [DicomConstraint](dicomconstraint). + +#### RegexConstraint + +This constraint contains a regular expression and a tag to test against. + +```json +{ + "Expression": string, a regular expression, suitable for the Regex constructor from .NET System.Text.RegularExpressions, + "Options": number, one of the RegexOptions from .NET System.Text.RegularExpressions, + "Ordinal": number, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": "RegexConstraint" +} +``` + +Where: + +- `Expression` is a regular expression compatible with [.NET Regex](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regex?view=net-5.0). + +- `Options` is the value of the enum in [.NET RegexOptions](https://docs.microsoft.com/en-us/dotnet/api/system.text.regularexpressions.regexoptions?view=net-5.0). + +- `Ordinal` is the ordinal to extract from the tag or -1 to extract all. + +- `Index` is as [DicomTagConstraint](#dicomtagconstraint). + +- `discriminator` is as [DicomConstraint](dicomconstraint). + +#### OrderedDateTimeConstraint, OrderedDoubleConstraint, OrderedIntConstraint, OrderedStringConstraint + +These contain an ordering function and a DICOM tag that the ordering function applies to. + +```json +{ + "Function": { + "Order": string, one of "Never", "LessThan", "Equal", "LessThanOrEqual", "GreaterThan", "NotEqual", "Always", + "Value": object, one of date/time, number, or for strings { + "Value": string, + "ComparisonType": number, 0 for case sensitive string comparisons, 1 for case insensitive. + }, + "Ordinal": 0 + }, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": string, one of "OrderedDateTimeConstraint", "OrderedDoubleConstraint", "OrderedIntConstraint", "OrderedStringConstraint" +} +``` + +For example: + +```json +{ + "Function": { + "Order": "Equal", + "Value": { + "Value": "HEAD", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 24, + "Element": 21 + }, + "discriminator": "OrderedStringConstraint" +} +``` + +Where: + +- `Function` is the ordering function to apply to the DICOM tag. + + - `Order` is the required relation between the supplied `Value` and the value of the DICOM tag. + + - `Value` is the value to test the DICOM tag against. + + - `Ordinal` is the ordinal to extract from the tag or -1 to extract all. + +- `Index` is as [DicomTagConstraint](#dicomtagconstraint). + +- `discriminator` is as [DicomConstraint](dicomconstraint). + +#### UIDStringOrderConstraint + +This is a variant of the OrderedDateTimeConstraint etc, but contains a constraint for a DICOM UID tag. + +```json +{ + "Function": { + "Order": string, order as above, + "Value": { + "Value": string, + "ComparisonType": number, as above + }, + "Ordinal": number + }, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": "UIDStringOrderConstraint" +} +``` + +For example: + +```json +{ + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + }, + "discriminator": "UIDStringOrderConstraint" +} +``` + +Where the `Function` and `Index` are as above, except that the constraint test is applied to a DICOM UID tag. + +#### TimeOrderConstraint + +This is a variant of the OrderedDateTimeConstraint etc, but for a tag of TM value representation. + +```json +{ + "Function": { + "Order": string, order as above, + "Value": number, a TimeSpan in .NET Timespan serialization format, + "Ordinal": number + }, + "Index": { + "Group": number, + "Element": number + }, + "discriminator": "TimeOrderConstraint" +} +``` + +For example: + +```json +{ + "Function": { + "Order": "GreaterThanOrEqual", + "Value": "16:05:42.7380000", + "Ordinal": 0 + }, + "Index": { + "Group": 25447, + "Element": 60116 + }, + "discriminator": "TimeOrderConstraint" +} +``` + +Where the `Function` and `Index` are as above, except that the constraint test is applied to a DICOM tag TimeOfDay property. + +## Licensing + +[MIT License](LICENSE) + +**You are responsible for the performance, the necessary testing, and if needed any regulatory clearance for + any of the models produced by this toolbox.** + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit [https://cla.opensource.microsoft.com](https://cla.opensource.microsoft.com/) . + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) . For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..f7b8998 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + \ No newline at end of file diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..dc72f0e --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,25 @@ +# TODO: The maintainer of this repo has not yet edited this file + +**REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? + +- **No CSS support:** Fill out this template with information about how to file issues and get help. +- **Yes CSS support:** Fill out an intake form at [aka.ms/spot](https://aka.ms/spot). CSS will work with/help you to determine next steps. More details also available at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). +- **Not sure?** Fill out a SPOT intake as though the answer were "Yes". CSS will help you decide. + +*Then remove this first heading from this SUPPORT.MD file before publishing your repo.* + +# Support + +## How to file issues and get help + +This project uses GitHub Issues to track bugs and feature requests. Please search the existing +issues before filing new issues to avoid duplicates. For new issues, file your bug or +feature request as a new Issue. + +For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE +FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER +CHANNEL. WHERE WILL YOU HELP PEOPLE?**. + +## Microsoft Support Policy + +Support for this **PROJECT or PRODUCT** is limited to the resources listed above. diff --git a/Source/Anonymizer/.editorconfig b/Source/Anonymizer/.editorconfig new file mode 100644 index 0000000..14df733 --- /dev/null +++ b/Source/Anonymizer/.editorconfig @@ -0,0 +1,207 @@ +# Rules in this file were initially inferred by Visual Studio IntelliCode from the C:\Users\v-jontri\source\repos\InnerEye-Gateway\Source\Microsoft.Gateway codebase based on best match to current usage at 18/03/2021 +# There already existed an .editorconfig file in this directory. Copy rules from this .editorconfig.inferred file to the existing .editorconfig file as desired to have them take effect at this location. +# You can modify the rules from these initially generated values to suit your own policies +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +[*.cs] + + +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation +indent_style = space + +#Formatting - indentation options + +#indent switch case contents. +csharp_indent_case_contents = true +#csharp_indent_case_contents_when_block +csharp_indent_case_contents_when_block = true +#indent switch labels +csharp_indent_switch_labels = true + +#Formatting - new line options + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require finally statements to be on a new line after the closing brace +csharp_new_line_before_finally = true +#require members of object initializers to be on the same line +csharp_new_line_before_members_in_object_initializers = false +#require braces to be on a new line for methods, lambdas, object_collection_array_initializers, control_blocks, and types (also known as "Allman" style) +csharp_new_line_before_open_brace = methods, lambdas, object_collection_array_initializers, control_blocks, types + +#Formatting - organize using options + +#sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +#Formatting - spacing options + +#require NO space between a cast and the value +csharp_space_after_cast = false +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true +#require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true +#remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false +#remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false +#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false +#remove space within empty parameter list parentheses for a method declaration +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true +#leave statements and member declarations on the same line +csharp_preserve_single_line_statements = true + +#Style - Code block preferences + +#prefer curly braces even for one line of code +csharp_prefer_braces = true:suggestion + +#Style - expression bodied member options + +#prefer block bodies for constructors +csharp_style_expression_bodied_constructors = false:suggestion +#prefer block bodies for methods +csharp_style_expression_bodied_methods = false:suggestion +#prefer expression-bodied members for properties +csharp_style_expression_bodied_properties = true:suggestion + +#Style - expression level options + +#prefer out variables to be declared before the method call +csharp_style_inlined_variable_declaration = false:suggestion +#prefer tuple names to ItemX properties +dotnet_style_explicit_tuple_names = true:suggestion +#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +#Style - Expression-level preferences + +#prefer default(T) over default +csharp_prefer_simple_default_expression = false:suggestion +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion +#prefer inferred tuple element names +dotnet_style_prefer_inferred_tuple_names = true:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - Miscellaneous preferences + +#prefer anonymous functions over local functions +csharp_style_pattern_local_over_anonymous_function = false:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,private,protected,internal,static,readonly,async,override,sealed,virtual,abstract:suggestion + +#Style - Pattern matching + +#prefer pattern matching instead of is expression with type casts +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +#Style - qualification options + +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion + +# CA1002: Do not expose generic lists +dotnet_diagnostic.CA1002.severity = suggestion + +# CA1008: Enums should have zero value +dotnet_diagnostic.CA1008.severity = suggestion + +# CA1014: Mark assemblies with CLSCompliant +dotnet_diagnostic.CA1014.severity = none + +# CA1027: Mark enums with FlagsAttribute +dotnet_diagnostic.CA1027.severity = suggestion + +# CA1028: Enum Storage should be Int32 +dotnet_diagnostic.CA1028.severity = suggestion + +# CA1031: Do not catch general exception types +dotnet_diagnostic.CA1031.severity = suggestion + +# CA1034: Nested types should not be visible +dotnet_diagnostic.CA1034.severity = suggestion + +# CA1052: Static holder types should be Static or NotInheritable +dotnet_diagnostic.CA1052.severity = suggestion + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = suggestion + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = suggestion + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = suggestion + +# CA1307: Specify StringComparison for clarity +dotnet_diagnostic.CA1307.severity = suggestion + +# CA1309: Use ordinal string comparison +dotnet_diagnostic.CA1309.severity = suggestion + +# CA1508: Avoid dead conditional code +dotnet_diagnostic.CA1508.severity = suggestion + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1707.severity = suggestion + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = suggestion + +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = suggestion + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = suggestion + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = suggestion + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = suggestion + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = suggestion + +# CA2234: Pass system uri objects instead of strings +dotnet_diagnostic.CA2234.severity = suggestion diff --git a/Source/Anonymizer/CodeCoverage.runsettings b/Source/Anonymizer/CodeCoverage.runsettings new file mode 100644 index 0000000..994e0db --- /dev/null +++ b/Source/Anonymizer/CodeCoverage.runsettings @@ -0,0 +1,24 @@ + + + + + + + + + + .*\.tests\..* + .*\.Tests\..* + .*\.core\..* + + + + + + + + + x64 + Framework45 + + \ No newline at end of file diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/AnonymisationTagHandler.cs b/Source/Anonymizer/DICOMAnonymizer.Tests/AnonymisationTagHandler.cs new file mode 100644 index 0000000..8cd40d7 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/AnonymisationTagHandler.cs @@ -0,0 +1,43 @@ +namespace DICOMAnonymizer.Tests +{ + using Dicom; + using DICOMAnonymizer; + using System; + using System.Collections.Generic; + using System.Text.RegularExpressions; + using AnonFunc = System.Func, Dicom.DicomItem, Dicom.DicomItem>; + + internal class AnonymisationTagHandler : ITagHandler + { + + /// + /// The anonymisation protocol. + /// + private readonly Dictionary _anonymisationProtocol = new Dictionary + { + { DicomTag.PatientID, (ds,tagOrIndexes, dicomItem)=> dicomItem }, + { DicomTag.Modality, (ds,tagOrIndexes, dicomItem)=> dicomItem }, + { DicomTag.SOPClassUID, (ds,tagOrIndexes, dicomItem)=> new DicomUniqueIdentifier(DicomTag.SOPClassUID,DicomUIDGenerator.GenerateDerivedFromUUID()) }, + { DicomTag.SOPInstanceUID, (ds,tagOrIndexes, dicomItem)=> new DicomUniqueIdentifier(DicomTag.SOPInstanceUID,DicomUIDGenerator.GenerateDerivedFromUUID()) }, + }; + + // TODO refactor into abstract class + public Dictionary GetConfiguration() => null; + + // TODO refactor into abstract class + public Dictionary, DicomItem, DicomItem>> GetRegexFuncs() => null; + + // TODO refactor into abstract class + public Dictionary GetTagFuncs() => _anonymisationProtocol; + + // TODO refactor into abstract class + public void NextDataset() + { + } + + public void Postprocess(DicomDataset newds) + { + // NOOP + } + } +} \ No newline at end of file diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/AnonymizerEngineTests.cs b/Source/Anonymizer/DICOMAnonymizer.Tests/AnonymizerEngineTests.cs new file mode 100644 index 0000000..81464c1 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/AnonymizerEngineTests.cs @@ -0,0 +1,443 @@ +using Dicom; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text.RegularExpressions; +using static DICOMAnonymizer.AnonymizeEngine; +using AnonFunc = System.Func, Dicom.DicomItem, Dicom.DicomItem>; + +namespace DICOMAnonymizer.Tests +{ + [TestClass] + public class AnonymizerEngineTests + { + private AnonymizeEngine GetAnonEngine(Mode m) + { + return (AnonymizeEngine)Activator.CreateInstance(typeof(AnonymizeEngine), BindingFlags.Instance | BindingFlags.NonPublic, null, new object[] { m, true }, null, null); + } + + private class TestHanlder : ITagHandler + { + public int State { get; set; } = 0; + + public TestHanlder() + { + tagh = new Dictionary(); + regh = new Dictionary(); + } + + public TestHanlder(Dictionary tagh, Dictionary regh) + { + this.tagh = tagh; + this.regh = regh; + } + + public Dictionary tagh = new Dictionary(); + public Dictionary regh = new Dictionary(); + + public Dictionary GetRegexFuncs() { return regh; } + + public Dictionary GetTagFuncs() { return tagh; } + + public void Postprocess(DicomDataset newds) { } + + public void NextDataset() { State++; } + + public Dictionary GetConfiguration() { return new Dictionary { { "c", "a" } }; } + } + + [TestMethod] + public void IgnoreNullReturnsInActions() + { + var anon = new AnonymizeEngine(Mode.inplace); + var th = new TestHanlder(null, null); + + anon.RegisterHandler(th); + anon.ForceRegisterHandler(th); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException), "A double registration of an action to a tag was prevented.")] + public void RegisterHandlerShouldNotAllowOverwriting() + { + var anon = GetAnonEngine(Mode.inplace); + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, (x, s, y) => { return y; } } }, null); + + anon.RegisterHandler(th); + anon.RegisterHandler(th); + } + + [TestMethod] + public void ForceRegisterHandlerShouldAllowOverwriting() + { + var anon = GetAnonEngine(Mode.clone); + var th = new TestHanlder(); + + // Execute the empty function + anon.RegisterHandler(th); + + var ds = new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")); + anon.Anonymize(ds); + + th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, (x, s, y) => { throw new Exception("Hello"); } } }, null); + anon.ForceRegisterHandler(th); + + // Execute the throwing function + try + { + anon.Anonymize(ds); + } + catch (Exception e) + { + Assert.AreEqual("Hello", e.Message); + } + } + + [TestMethod] + public void NestedSequencesInPlace() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.inplace); + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, (x, s, y) => { return new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "3.2.1"); } } }, null); + anon.RegisterHandler(th); + + anon.Anonymize(ds); + + Assert.AreEqual("3.2.1", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("3.2.1", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + } + + [TestMethod] + public void NestedSequencesBlank_UndeclaredTagsShouldBeRemoved() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.blank); + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, (x, s, y) => { return new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "3.2.1"); } } }, null); + anon.RegisterHandler(th); + + var nds = anon.Anonymize(ds); + + // Assert no changes happened to the original object + Assert.AreEqual("1.2.3", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(ds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("1.2.3", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + + Assert.AreEqual("3.2.1", nds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsFalse(nds.Contains(DicomTag.RTROIObservationsSequence)); + } + + [TestMethod] + public void NestedSequencesBlank_DeclaredTagsShouldBeKept() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.blank); + var th = new TestHanlder(new Dictionary() { + { DicomTag.RTROIObservationsSequence, (x, s, y) => { return y; } }, + { DicomTag.SOPInstanceUID, (x, s, y) => { return new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "3.2.1"); } } + }, null); + anon.RegisterHandler(th); + + var nds = anon.Anonymize(ds); + + // Assert no changes happened to the original object + Assert.AreEqual("1.2.3", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(ds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("1.2.3", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + + Assert.AreEqual("3.2.1", nds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.AreEqual(1, nds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("3.2.1", nds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + } + + [TestMethod] + public void NestedSequencesClone_UndeclaredTagsShouldBeKept() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.clone); + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, (x, s, y) => { return new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "3.2.1"); } } }, null); + anon.RegisterHandler(th); + + var nds = anon.Anonymize(ds); + + // Assert no changes happened to the original object + Assert.AreEqual("1.2.3", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(ds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("1.2.3", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + + Assert.AreEqual("3.2.1", nds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(nds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, nds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("3.2.1", nds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + } + + [TestMethod] + public void NestedSequencesClone_OneDeclaredTagShouldBeRemoved() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.clone); + var th = new TestHanlder(new Dictionary() { + { DicomTag.RTROIObservationsSequence, (x, s, y) => { return null; } }, + { DicomTag.SOPInstanceUID, (x, s, y) => { return new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "3.2.1"); } } + }, null); + anon.RegisterHandler(th); + + var nds = anon.Anonymize(ds); + + // Assert no changes happened to the original object + Assert.AreEqual("1.2.3", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(ds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("1.2.3", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + + Assert.AreEqual("3.2.1", nds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsFalse(nds.Contains(DicomTag.RTROIObservationsSequence)); + } + + [TestMethod] + public void NestedSequencesClone_RemovalAllExceptSequence() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.clone); + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, (x, s, y) => { return null; } } }, null); + anon.RegisterHandler(th); + + var nds = anon.Anonymize(ds); + + // Assert no changes happened to the original object + Assert.AreEqual("1.2.3", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(ds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("1.2.3", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + + Assert.IsFalse(nds.Contains(DicomTag.SOPInstanceUID)); + Assert.IsTrue(nds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(0, nds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + } + + [TestMethod] + public void NestedSequencesBlank_RemovalAllExceptSequence() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))); + + var anon = GetAnonEngine(Mode.blank); + var th = new TestHanlder(new Dictionary() { { DicomTag.RTROIObservationsSequence, (x, s, y) => { return y; } } }, null); + anon.RegisterHandler(th); + + var nds = anon.Anonymize(ds); + + // Assert no changes happened to the original object + Assert.AreEqual("1.2.3", ds.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.IsTrue(ds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(1, ds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + Assert.AreEqual("1.2.3", ds.GetSequence(DicomTag.RTROIObservationsSequence).Items[0].GetSingleValue(DicomTag.SOPInstanceUID)); + + Assert.IsFalse(nds.Contains(DicomTag.SOPInstanceUID)); + Assert.IsTrue(nds.Contains(DicomTag.RTROIObservationsSequence)); + Assert.AreEqual(0, nds.GetSequence(DicomTag.RTROIObservationsSequence).Items.Count); + } + + [TestMethod] + public void HandlerOverwriteReporting() + { + var anon = GetAnonEngine(Mode.clone); + var th = new TestHanlder(new Dictionary() { + { DicomTag.RTROIObservationsSequence, (x, s, y) => { return y; } }, + { DicomTag.SOPInstanceUID, (x, s, y) => { return y; } } + }, + new Dictionary() { + { new Regex(".*"), (x, s, y) => { return y; }}, + { new Regex(".*2"), (x, s, y) => { return y; } }, + }); + anon.RegisterHandler(th); + var report = anon.ForceRegisterHandler(th); + + Assert.AreEqual(4, report.Count); + var exp = DicomTag.RTROIObservationsSequence.DictionaryEntry.Name + " " + DicomTag.RTROIObservationsSequence.ToString().ToUpper(); + Assert.AreEqual(exp, report[0]); + Assert.AreEqual(".*", report[2]); + } + + [TestMethod] + public void RegistreredHandlersReport() + { + var anon = new AnonymizeEngine(Mode.clone); + var cp = new ConfidentialityProfile(); + anon.RegisterHandler(cp); + + var report = anon.ReportRegisteredHandlers(); + + Assert.AreEqual(336, report.Count); + } + + [TestMethod] + public void TestDicomFile() + { + var file = DicomFile.Open(@"TestData/CT1_J2KI"); + + var anonymizer = GetAnonEngine(Mode.clone); + var cp = new ConfidentialityProfile(); + anonymizer.RegisterHandler(cp); + + var prev = file.FileMetaInfo.MediaStorageSOPInstanceUID; + Assert.AreNotEqual("", prev.ToString()); + + var nf = anonymizer.Anonymize(file); + + Assert.AreEqual(0, nf.Dataset.GetValues(DicomTag.PatientName).Length); + Assert.AreEqual(0, nf.Dataset.GetValues(DicomTag.PatientID).Length); + Assert.AreEqual(0, nf.Dataset.GetValues(DicomTag.PatientSex).Length); + Assert.AreNotEqual(prev.ToString(), nf.FileMetaInfo.MediaStorageSOPInstanceUID.ToString()); + Assert.IsNull(nf.FileMetaInfo.ImplementationVersionName); + Assert.IsNull(nf.FileMetaInfo.SourceApplicationEntityTitle); + Assert.AreEqual(file.FileMetaInfo.TransferSyntax, nf.FileMetaInfo.TransferSyntax); + } + + [TestMethod] + public void TestDicomFileBlank() + { + var file = DicomFile.Open(@"TestData/CT1_J2KI"); + + var anonymizer = GetAnonEngine(Mode.blank); + var cp = new AnonymisationTagHandler(); + anonymizer.RegisterHandler(cp); + + var prev = file.FileMetaInfo.MediaStorageSOPInstanceUID; + Assert.AreNotEqual("", prev.ToString()); + + var nf = anonymizer.Anonymize(file); + Assert.AreEqual(file.Dataset.GetSingleValue(DicomTag.PatientID), nf.Dataset.GetSingleValue(DicomTag.PatientID)); + Assert.AreEqual(file.Dataset.GetSingleValue(DicomTag.Modality), nf.Dataset.GetSingleValue(DicomTag.Modality)); + Assert.AreNotEqual(file.Dataset.GetSingleValue(DicomTag.SOPInstanceUID), nf.Dataset.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.AreNotEqual(file.Dataset.GetSingleValue(DicomTag.SOPClassUID), nf.Dataset.GetSingleValue(DicomTag.SOPClassUID)); + Assert.AreNotEqual(prev.ToString(), nf.FileMetaInfo.MediaStorageSOPInstanceUID.ToString()); + Assert.IsNull(nf.FileMetaInfo.ImplementationVersionName); + Assert.IsNull(nf.FileMetaInfo.SourceApplicationEntityTitle); + Assert.AreEqual(file.FileMetaInfo.TransferSyntax, nf.FileMetaInfo.TransferSyntax); + } + + [TestMethod] + public void TestDicomMissingTransferSyntax() + { + var file = DicomFile.Open(@"TestData/CT1_J2KI"); + file.FileMetaInfo.Remove(DicomTag.TransferSyntaxUID); + var anonymizer = GetAnonEngine(Mode.blank); + var cp = new AnonymisationTagHandler(); + anonymizer.RegisterHandler(cp); + var prev = file.FileMetaInfo.MediaStorageSOPInstanceUID; + Assert.AreNotEqual("", prev.ToString()); + + var nf = anonymizer.Anonymize(file); + + Assert.AreEqual(file.Dataset.GetSingleValue(DicomTag.PatientID), nf.Dataset.GetSingleValue(DicomTag.PatientID)); + Assert.AreEqual(file.Dataset.GetSingleValue(DicomTag.Modality), nf.Dataset.GetSingleValue(DicomTag.Modality)); + Assert.AreNotEqual(file.Dataset.GetSingleValue(DicomTag.SOPInstanceUID), nf.Dataset.GetSingleValue(DicomTag.SOPInstanceUID)); + Assert.AreNotEqual(file.Dataset.GetSingleValue(DicomTag.SOPClassUID), nf.Dataset.GetSingleValue(DicomTag.SOPClassUID)); + Assert.AreNotEqual(prev.ToString(), nf.FileMetaInfo.MediaStorageSOPInstanceUID.ToString()); + Assert.IsNull(nf.FileMetaInfo.ImplementationVersionName); + Assert.IsNull(nf.FileMetaInfo.SourceApplicationEntityTitle); + Assert.IsFalse(file.FileMetaInfo.Contains(DicomTag.TransferSyntaxUID)); + Assert.AreEqual(DicomTransferSyntax.ExplicitVRLittleEndian, nf.FileMetaInfo.TransferSyntax); + } + + [TestMethod] + public void TestNextDataset() + { + var ds = new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")); + var anon = GetAnonEngine(Mode.clone); + AnonFunc f = (o, s, i) => { return null; }; + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, f } }, null); + anon.RegisterHandler(th); + + anon.Anonymize(ds); + Assert.AreEqual(1, th.State); + + anon.Anonymize(ds); + Assert.AreEqual(2, th.State); + } + + [TestMethod] + public void TestTagPath() + { + var ds = new DicomDataset( + new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.RTROIObservationsSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3"), + new DicomSequence(DicomTag.ReferenceBasisCodeSequence, + new DicomDataset(new DicomUniqueIdentifier(DicomTag.SOPInstanceUID, "1.2.3")))))); + + var anon = GetAnonEngine(Mode.inplace); + + int d = 0; + AnonFunc f = (o, s, i) => + { + if (d == 0) + { + Assert.AreEqual(0, s.Count); + d++; + } + else if (d == 1) + { + Assert.AreEqual(2, s.Count); + Assert.AreEqual(DicomTag.RTROIObservationsSequence, s[0].Tag); + Assert.IsTrue(s[0].IsTag); + Assert.AreEqual(0, s[1].Index); + Assert.IsFalse(s[1].IsTag); + d++; + } + else if (d == 2) + { + Assert.AreEqual(4, s.Count); + Assert.AreEqual(DicomTag.RTROIObservationsSequence, s[0].Tag); + Assert.AreEqual(DicomTag.ReferenceBasisCodeSequence, s[2].Tag); + } + return i; + }; + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, f } }, null); + anon.RegisterHandler(th); + + anon.Anonymize(ds); + } + + [TestMethod] + [ExpectedException(typeof(FormatException))] + public void TestDescriptionIsRequired() + { + var anon = new AnonymizeEngine(Mode.clone); + AnonFunc f = (o, s, i) => { return null; }; + var th = new TestHanlder(new Dictionary() { { DicomTag.SOPInstanceUID, f } }, null); + anon.RegisterHandler(th); + } + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/ConfidentialityProfileTests.cs b/Source/Anonymizer/DICOMAnonymizer.Tests/ConfidentialityProfileTests.cs new file mode 100644 index 0000000..01515ec --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/ConfidentialityProfileTests.cs @@ -0,0 +1,168 @@ +using Dicom; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using static DICOMAnonymizer.AnonymizeEngine; +using static DICOMAnonymizer.ConfidentialityProfile; + +namespace DICOMAnonymizer.Tests +{ + [TestClass] + public class ConfidentialityProfileTests + { + [TestMethod] + public void CheckEmptyStringComparison() + { + var engine = new AnonymizeEngine(Mode.clone); + + var profileOpts = SecurityProfileOptions.BasicProfile + | SecurityProfileOptions.RetainLongModifDates + | SecurityProfileOptions.CleanDesc; + + var cp = new ConfidentialityProfile(profileOpts); + + engine.RegisterHandler(cp); + } + + [TestMethod] + public void AnonymizeInPlace_Dataset_PatientDataEmpty() + { + var dataset = DicomFile.Open(@"TestData/CT1_J2KI").Dataset; + + var anonymizer = new AnonymizeEngine(Mode.inplace); + var cp = new ConfidentialityProfile(); + anonymizer.RegisterHandler(cp); + + anonymizer.Anonymize(dataset); + + Assert.AreEqual(0, dataset.GetValues(DicomTag.PatientName).Length); + Assert.AreEqual(0, dataset.GetValues(DicomTag.PatientID).Length); + Assert.AreEqual(0, dataset.GetValues(DicomTag.PatientSex).Length); + } + + [TestMethod] + public void AnonymizeInPlace_File_SopInstanceUidTransferredToMetaInfo() + { + var file = DicomFile.Open(@"TestData/CT1_J2KI"); + var old = file.Dataset.GetSingleValue(DicomTag.SOPInstanceUID); + + var anonymizer = new AnonymizeEngine(Mode.inplace); + var cp = new ConfidentialityProfile(); + anonymizer.RegisterHandler(cp); + + file = anonymizer.Anonymize(file); + + var expected = file.Dataset.GetSingleValue(DicomTag.SOPInstanceUID); + var actual = file.FileMetaInfo.MediaStorageSOPInstanceUID; + Assert.AreNotEqual(expected, old); + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void AnonymizeInPlace_File_ImplementationVersionNameNotMaintained() + { + var file = DicomFile.Open(@"TestData/CT1_J2KI"); + var expected = file.FileMetaInfo.ImplementationVersionName; + + Assert.IsFalse(string.IsNullOrEmpty(expected)); + + var anonymizer = new AnonymizeEngine(Mode.inplace); + var cp = new ConfidentialityProfile(); + anonymizer.RegisterHandler(cp); + + file = anonymizer.Anonymize(file); + + var actual = file.FileMetaInfo.ImplementationVersionName; + Assert.IsNull(actual); + } + + [TestMethod] + public void Anonymize_Dataset_OriginalDatasetNotModified() + { + var dataset = DicomFile.Open(@"TestData/CT-MONO2-16-ankle").Dataset; + var expected = dataset.GetSingleValue(DicomTag.StudyInstanceUID); + + var anonymizer = new AnonymizeEngine(Mode.clone); + var cp = new ConfidentialityProfile(); + anonymizer.RegisterHandler(cp); + + var newDataset = anonymizer.Anonymize(dataset); + + var actual = dataset.GetSingleValue(DicomTag.StudyInstanceUID); + var actualNew = newDataset.GetSingleValue(DicomTag.StudyInstanceUID); + + Assert.AreEqual(expected, actual); + Assert.AreNotEqual(expected, actualNew); + } + + [TestMethod] + public void CheckRemovals() + { + var ds = new DicomDataset(new DicomShortString(DicomTag.PerformedProcedureStepID, "123")); + + var cp = new ConfidentialityProfile(); + + var anonymizer = new AnonymizeEngine(Mode.blank); + anonymizer.RegisterHandler(cp); + var nds_blank = anonymizer.Anonymize(ds); + Assert.IsFalse(nds_blank.Contains(DicomTag.PerformedProcedureStepID)); + Assert.AreEqual("123", ds.GetSingleValue(DicomTag.PerformedProcedureStepID)); + + anonymizer = new AnonymizeEngine(Mode.clone); + anonymizer.RegisterHandler(cp); + var nds_clone = anonymizer.Anonymize(ds); + Assert.IsFalse(nds_clone.Contains(DicomTag.PerformedProcedureStepID)); + Assert.AreEqual("123", ds.GetSingleValue(DicomTag.PerformedProcedureStepID)); + + anonymizer = new AnonymizeEngine(Mode.inplace); + anonymizer.RegisterHandler(cp); + anonymizer.Anonymize(ds); + Assert.IsFalse(ds.Contains(DicomTag.PerformedProcedureStepID)); + + } + + [TestMethod] + public void CheckThereIsNoGlobalCachedState() + { + var anonymizer = new AnonymizeEngine(Mode.inplace); + var cp = new ConfidentialityProfile(SecurityProfileOptions.BasicProfile); + anonymizer.RegisterHandler(cp); + + var ds = new DicomDataset(new DicomUniqueIdentifier(DicomTag.RequestedSOPInstanceUID, "123")); + + anonymizer.Anonymize(ds); + Assert.AreNotEqual("123", ds.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + + cp = new ConfidentialityProfile(SecurityProfileOptions.BasicProfile | SecurityProfileOptions.RetainUIDs); + anonymizer.ForceRegisterHandler(cp); + + ds = new DicomDataset(new DicomUniqueIdentifier(DicomTag.RequestedSOPInstanceUID, "123")); + + anonymizer.Anonymize(ds); + Assert.AreEqual("123", ds.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + } + + [TestMethod] + public void CheckWhitelistTags() + { + var cp = new ConfidentialityProfile(SecurityProfileOptions.BasicProfile | SecurityProfileOptions.RetainUIDs); + + var ds = new DicomDataset(new DicomUniqueIdentifier(DicomTag.RequestedSOPInstanceUID, "123")); + + var anonymizer = new AnonymizeEngine(Mode.blank); + anonymizer.RegisterHandler(cp); + var nds_blank = anonymizer.Anonymize(ds); + Assert.AreEqual("123", ds.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + Assert.AreEqual("123", nds_blank.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + + anonymizer = new AnonymizeEngine(Mode.clone); + anonymizer.RegisterHandler(cp); + var nds_clone = anonymizer.Anonymize(ds); + Assert.AreEqual("123", ds.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + Assert.AreEqual("123", nds_clone.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + + anonymizer = new AnonymizeEngine(Mode.inplace); + anonymizer.RegisterHandler(cp); + anonymizer.Anonymize(ds); + Assert.AreEqual("123", ds.GetSingleValue(DicomTag.RequestedSOPInstanceUID)); + } + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/DICOMAnonymizer.Tests.csproj b/Source/Anonymizer/DICOMAnonymizer.Tests/DICOMAnonymizer.Tests.csproj new file mode 100644 index 0000000..1a3b069 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/DICOMAnonymizer.Tests.csproj @@ -0,0 +1,44 @@ + + + net462 + x64 + DICOMAnonymizer.Tests + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Tests for DICOMAnonymizer for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + + + Always + + + Always + + + Always + + + Always + + + \ No newline at end of file diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/SpecTests.cs b/Source/Anonymizer/DICOMAnonymizer.Tests/SpecTests.cs new file mode 100644 index 0000000..40802ad --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/SpecTests.cs @@ -0,0 +1,233 @@ +using DICOMAnonymizer.Tools; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +namespace DICOMAnonymizer.Tests +{ + [TestClass] + public class SpecTests + { + private const string cProfPath = @"SpecXML\part15.xml"; + private const string serClassSpecPath = @"SpecXML\part04.xml"; + + // We need { } around the namespace or else ':' can not be parsed as a XName + private const string ns = @"{http://docbook.org/ns/docbook}"; + private readonly XDocument cProfXML = XDocument.Load(cProfPath); + private readonly XDocument serClassSpecXML = XDocument.Load(serClassSpecPath); + + [TestMethod] + public void SpecExists() + { + Assert.AreEqual(16022, cProfXML.Descendants().Count()); + Assert.AreEqual(32531, serClassSpecXML.Descendants().Count()); + } + + [TestMethod] + public void CheckSpecXMLNamespace() + { + // We need { } around the name space or else ':' can not be parsed as a XName + Assert.AreEqual(ns, "{" + cProfXML.Root.GetDefaultNamespace().NamespaceName + "}"); + Assert.AreEqual(ns, "{" + serClassSpecXML.Root.GetDefaultNamespace().NamespaceName + "}"); + } + + /// + /// Check if our local Spec version is the same with the latest remote. + /// If this test fails, updating: 1) the DICOM Spec file and 2) ConfidentialityProfile + /// Tags is required. + /// To check if we are consistent with the latest version, we use the remote + /// ETag to avoid downloading the whole Spec. + /// If the remote ETag is different, the test is marked as inconclusive and + /// we download the whole remote file to check it against the local. If the + /// files are the same, then our ETag needs to be updated. Else, the + /// test should fail. + /// + [Ignore] + [TestMethod] + public void CheckConfidentialityProfileWithLatestOnline() + { + var specUrl = @"http://dicom.nema.org/medical/dicom/current/source/docbook/part15/part15.xml"; + + WebRequest request = WebRequest.Create(specUrl); + request.Method = "HEAD"; + var etag = request.GetResponse().Headers["etag"].Replace(':', '-'); + + if (!etag.Equals("\"7732d244af7d21-0\"")) + { + WebClient client = new WebClient(); + using (var reader = new StreamReader(client.OpenRead(specUrl))) + { + string onlineSpec = reader.ReadToEnd(); + + string localSpec = File.ReadAllText(cProfPath); + Assert.AreEqual(localSpec, onlineSpec); + } + + Assert.Inconclusive(); + } + } + + [Ignore] + [TestMethod] + public void CheckServiceClassWithLatestOnline() + { + var specUrl = @"http://dicom.nema.org/medical/dicom/current/source/docbook/part04/part04.xml"; + + WebRequest request = WebRequest.Create(specUrl); + request.Method = "HEAD"; + var etag = request.GetResponse().Headers["etag"].Replace(':', '-'); + + if (!etag.Equals("\"bf03da46f7d21-0\"")) + { + WebClient client = new WebClient(); + using (var reader = new StreamReader(client.OpenRead(specUrl))) + { + string onlineSpec = reader.ReadToEnd(); + + string localSpec = File.ReadAllText(serClassSpecPath); + Assert.AreEqual(localSpec, onlineSpec); + } + + Assert.Inconclusive(); + } + } + + [TestMethod] + public void DefaultConfidentialityProfile() + { + Func byID = y => y.Name.LocalName.Equals("id"); + Func tableName = y => y.Attributes().Where(byID).First().Value; + + var table = cProfXML.Descendants() + .Where(x => x.Name.LocalName.Equals("table")) + .Where(x => tableName(x).Equals("table_E.1-1")) + .First(); + // Make sure our table has 14 columns + Assert.AreEqual(14, table.Element(ns + "thead").Descendants().Where(x => x.Name.LocalName.Equals("para")).Count()); + + var rows = table.Element(ns + "tbody").Elements(ns + "tr"); + Assert.AreEqual(278, rows.Count()); + + var tagProf = new List(276); + var regProf = new List(4); + foreach (var row in rows) + { + bool isTag = false; + StringBuilder sb = new StringBuilder(); + var clms = row.Elements().ToList(); + + var parenthesis = new[] { '(', ')' }; + var tag = clms[1].Value.Trim(parenthesis); + + // tags might be dirty or might need to be converted into regex + if (tag.Equals("50xx,xxxx")) + { + tag = "50[0-9A-F]{2},[0-9A-F]{4}"; + } + else if (tag.Equals("60xx,4000")) + { + tag = "60[0-9A-F]{2},4000"; + } + else if (tag.Equals("60xx,3000")) + { + tag = "60[0-9A-F]{2},3000"; + } + else if (tag.Equals("gggg,eeee) where gggg is odd")) + { + tag = "[0-9A-F]{3}[13579BDF],[0-9A-F]{4}"; + } + else + { + if (tag.Contains(" ")) + { + tag = tag.Replace(" ", string.Empty); + } + + Regex r = new Regex("[0-9A-F]{4},[0-9A-F]{4}"); + Assert.IsTrue(r.IsMatch(tag)); + isTag = true; + } + sb.Append(tag); + + for (int i = 4; i < clms.Count; i++) + { + sb.Append(";" + clms[i].Value); + } + + (isTag ? tagProf : regProf).Add(sb.ToString()); + } + + tagProf.Sort(); + regProf.Sort(); + + var rtagProf = new List(ConfidentialityProfile.TagProfile); + rtagProf.Sort(); + var rregProf = new List(ConfidentialityProfile.RegexProfile); + rregProf.Sort(); + + // Check our Confidentiality Profile is in sync with the newly parsed Spec + Assert.IsTrue(Enumerable.SequenceEqual(tagProf, rtagProf)); + Assert.IsTrue(Enumerable.SequenceEqual(regProf, rregProf)); + } + + [TestMethod] + public void SOPClasses() + { + Func byID = y => y.Name.LocalName.Equals("id"); + Func tableName = y => y.Attributes().Where(byID).First().Value; + + var table = serClassSpecXML.Descendants() + .Where(x => x.Name.LocalName.Equals("table")) + .Where(x => tableName(x).Equals("table_B.5-1")) + .First(); + // Make sure our table has 3 columns + Assert.AreEqual(3, table.Element(ns + "thead").Descendants().Where(x => x.Name.LocalName.Equals("para")).Count()); + + var rows = table.Element(ns + "tbody").Elements(ns + "tr"); + Assert.AreEqual(129, rows.Count()); + + var enumNames = Enum.GetValues(typeof(SOPClass)).Cast().Select(e => e.ToString()).ToList(); + + var specSOPNames = new List(129); + var specSOPCodes = new List(129); + foreach (var row in rows) + { + var name = row.Elements().ElementAt(0); + var pad = ""; + if (name.Value.Length > 0 && char.IsDigit(name.Value[0])) + { + pad = "_"; + } + var cleanName = name.Value.Replace(" ", string.Empty).Replace("-", string.Empty).Replace("/", string.Empty); + specSOPNames.Add(pad + cleanName); + + var code = row.Elements().ElementAt(1); + specSOPCodes.Add(code.Value); + } + Assert.IsTrue(Enumerable.SequenceEqual(specSOPNames, enumNames)); + + // Check conversions from name -> code, and vice versa + for (int i = 0; i < specSOPNames.Count; i++) + { + var name = SOPClassFinder.SOPClassName(specSOPCodes[i]); + Assert.AreEqual(specSOPNames[i], name.ToString()); + + var classname = (SOPClass)Enum.Parse(typeof(SOPClass), specSOPNames[i]); + var code = SOPClassFinder.SOPClassCode(classname); + Assert.AreEqual(specSOPCodes[i], code); + } + + // Check for unknown values + var o1 = SOPClassFinder.SOPClassName("test"); + Assert.IsNull(o1); + var o2 = SOPClassFinder.SOPClassCode((SOPClass)254); + Assert.IsNull(o2); + } + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part04.xml b/Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part04.xml new file mode 100644 index 0000000..0adf5a2 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part04.xml @@ -0,0 +1,47684 @@ + + + PS3.4 + DICOM PS3.4 2017c - Service Class Specifications + + + DICOM Standards Committee + + + 2017 + NEMA + + + + Notice and Disclaimer + The information in this publication was considered technically sound by the consensus of persons engaged in the development and approval of the document at the time it was developed. Consensus does not necessarily mean that there is unanimous agreement among every person participating in the development of this document. + NEMA standards and guideline publications, of which the document contained herein is one, are developed through a voluntary consensus standards development process. This process brings together volunteers and/or seeks out the views of persons who have an interest in the topic covered by this publication. While NEMA administers the process and establishes rules to promote fairness in the development of consensus, it does not write the document and it does not independently test, evaluate, or verify the accuracy or completeness of any information or the soundness of any judgments contained in its standards and guideline publications. + NEMA disclaims liability for any personal injury, property, or other damages of any nature whatsoever, whether special, indirect, consequential, or compensatory, directly or indirectly resulting from the publication, use of, application, or reliance on this document. NEMA disclaims and makes no guaranty or warranty, expressed or implied, as to the accuracy or completeness of any information published herein, and disclaims and makes no warranty that the information in this document will fulfill any of your particular purposes or needs. NEMA does not undertake to guarantee the performance of any individual manufacturer or seller's products or services by virtue of this standard or guide. + In publishing and making this document available, NEMA is not undertaking to render professional or other services for or on behalf of any person or entity, nor is NEMA undertaking to perform any duty owed by any person or entity to someone else. Anyone using this document should rely on his or her own independent judgment or, as appropriate, seek the advice of a competent professional in determining the exercise of reasonable care in any given circumstances. Information and other standards on the topic covered by this publication may be available from other sources, which the user may wish to consult for additional views or information not covered by this publication. + NEMA has no power, nor does it undertake to police or enforce compliance with the contents of this document. NEMA does not certify, test, or inspect products, designs, or installations for safety or health purposes. Any certification or other statement of compliance with any health or safety-related information in this document shall not be attributable to NEMA and is solely the responsibility of the certifier or maker of the statement. + + + Foreword + This DICOM Standard was developed according to the procedures of the DICOM Standards Committee. + The DICOM Standard is structured as a multi-part document using the guidelines established in . + DICOM® is the registered trademark of the National Electrical Manufacturers Association for its standards publications relating to digital communications of medical information, all rights reserved. + HL7® and CDA® are the registered trademarks of Health Level Seven International, all rights reserved. + SNOMED®, SNOMED Clinical Terms®, SNOMED CT® are the registered trademarks of the International Health Terminology Standards Development Organisation (IHTSDO), all rights reserved. + LOINC® is the registered trademark of Regenstrief Institute, Inc, all rights reserved. + + + Scope and Field of Application + This Part of the DICOM Standard specifies the set of Service Class Definitions that provide an abstract definition of real-world activities applicable to communication of digital medical information. For each Service Class Definition, this Part specifies: + + + the semantic description of the activities of the Service Class Definition + + + the group of DIMSE Service operations and notifications applicable to the Service Class Description + + + one or more functionally-related Service-Object Pair (SOP) Classes that are supported by the Service Class Definition and may be performed between peer DICOM Application Entities + + + the relationship of each Service-Object Pair (SOP) Classes to applicable Information Object Definitions specified in . + + + For each Service Class Definition, this Part does not specify: + + + any necessary information for the semantic description of the IOD + + + relationships to associated real-world objects relevant to the IOD + + + Attributes that describe the characteristics of the IOD + + + This Part is related to other parts of the DICOM Standard in that: + + + + specifies the set of Information Object Definitions to which the services defined in this Part may be applied + + + + defines the data encoding used in the DIMSE Protocol when applied to IODs defined in this Part + + + + contains an index by Tag of all IOD Attributes defined in this Part. This index includes the Value Representation and Value Multiplicity for each Attribute + + + + defines the DIMSE Services and Protocol that may be applied to IODs defined in this Part. + + + + + Normative References + The following standards contain provisions which, through reference in this text, constitute provisions of this Standard. At the time of publication, the editions indicated were valid. All standards are subject to revision, and parties to agreements based on this Standard are encouraged to investigate the possibilities of applying the most recent editions of the standards indicated below. + + + ISO/IEC Directives, Part 2 + + ISO/IEC + + 2016/05 + 7.0 + Rules for the structure and drafting of International Standards + + + + + + ISO 7498-1 + + ISO + + 1994 + Information Processing Systems - Open Systems Interconnection - Basic Reference Model + + + ISO/TR 8509 + + ISO + + Information Processing Systems - Open Systems Interconnection - Service Conventions + + ISO/TR 8509 has been withdrawn. See ISO/IEC 2382-26:1993 Information technology - Vocabulary - Part 26: Open systems interconnection + + + + RFC7230 + + IETF + + June 2014 + Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing + + + + + + Porter and Duff 1984 + + Computer Graphics + + + + + Porter, Thomas + + + Duff, Tom + + + 1984 + 18 + 3 + 253-259 + Compositing Digital Images + 10.1145/800031.808606 + + + + + + + + + Definitions + For the purposes of this Standard the following definitions apply. +
+ Reference Model Definitions + This Part of the Standard makes use of the following terms defined in ISO 7498-1: + + + Application Entity + + + Service or Layer Service + + + Application Entity Title + + +
+
+ Service Conventions Definitions + This Part of the Standard makes use of the following terms defined in ISO/TR 8509: + + + Primitive + + +
+
+ DICOM Introduction and Overview Definitions + This Part of the Standard makes use of the following terms defined in : + + + Attribute + + + Command + + + Data Dictionary + + + Information Object + + + Message + + +
+
+ DICOM Upper Layer Service Definitions + This Part of the Standard makes use of the following terms defined in : + + + Unique Identifier (UID) + + + DICOM Upper Layer Service + + +
+
+ DICOM Message Exchange Definitions + This Part of the Standard makes use of the following terms defined in : + + + DICOM Message Service Element (DIMSE) + + + DIMSE-N Services + + + DIMSE-C Services + + + DIMSE Service Group (DSG) + + +
+
+ DICOM Information Object Definitions + This Part of the Standard makes use of the following terms defined in : + + + Attribute Tag + + + Composite IOD + + + DICOM Application Model + + + DICOM Information Model + + + Information Object Definition + + + Module + + + Normalized IOD + + + Functional Group + + +
+
+ DICOM Conformance + This Part of the Standard makes use of the following terms defined in : + + + Standard SOP Class + + + Specialized SOP Class + + + Conformance Statement + + +
+
+ DICOM Data Structures and Encoding + This Part of the Standard makes use of the following terms defined in : + + + Data Element + + + Data Set + + +
+
+ DICOM Service Class Definitions + The following definitions are commonly used in this Part of the DICOM Standard: + + Classic Image Storage SOP Class: an Image Storage SOP Class that is defined by an IOD that stores a single frame and defines the majority of the Attributes in the top-level Data Set. + + Combined Print Image: a pixel matrix created by superimposing an image and an overlay, the size of which is defined by the smallest rectangle enclosing the superimposed image and overlay. + + DICOM Information Model: an Entity-Relationship diagram that is used to model the relationships between the Information Object Definitions representing classes of Real-World Objects defined by the DICOM Application Model. + + DICOM Application Model: an Entity-Relationship diagram used to model the relationships between Real-World Objects that are within the area of interest of the DICOM Standard. + + Enhanced Image Storage SOP Class: an Image Storage SOP Class that is defined by an IOD that stores multiple frames and defines the majority of the Attributes in Functional Group Sequences. + + Legacy Converted Enhanced Image Storage SOP Class: a modality-specific Enhanced Image Storage SOP Class that is defined by an IOD that defines only generic Functional Group Sequences, which does not require information that is not present in Classic Image Storage SOP Class Instances, and is intended for storage of converted Classic Image Storage SOP Class Instances when there is insufficient information to use a True Enhanced Image Storage SOP Class. + + Meta Service-Object Pair (SOP) Class: a pre-defined set of SOP Classes that may be associated under a single SOP for the purpose of negotiating the use of the set with a single item. + + Performed Procedure Step SOP Class: any SOP Class that encodes the details about the performance of a procedure step. + + Performed Procedure Step SOP Instance: an instance of a Performed Procedure Step SOP Class. Note that all UPS instances are instances of the UPS Push SOP Class, which is capable of encoding details about the performance of a procedure step (in addition to details about the scheduled procedure step) and thus qualify as an instance of a Performed Procedure Step SOP Class. + + Preformatted Grayscale Image: an image where all annotation, graphics, and grayscale transformations (up to and including the VOI LUT) expected in the printed image have been burnt in or applied before being sent to the SCP. It is a displayable image where the polarity of the intended display is specified by Photometric Interpretation (0028,0004). + + Preformatted Color Image: an image where all annotation, graphics, and color transformations expected in the printed image have been burnt in or applied before being sent to the SCP. + + Real-World Activity: that which exists in the real world that pertains to specific area of information processing within the area of interest of the DICOM Standard. Such a Real-World Activity may be represented by one or more computer information metaphors called SOP Classes. + + Real-World Object: that which exists in the real world upon which operations may be performed that are within the area of interest of the DICOM Standard. Such a Real-World Object may be represented through a computer information metaphor called a SOP Instance. + + Related General SOP Class: a SOP Class that is related to another SOP Class as being more generalized in terms of behavior defined in the standard, and that may be used to identically encode an instance with the same Attributes and values, other than the SOP Class UID. In particular, this may be the SOP Class from which a Specialized SOP Class (see ) is derived. + + Service Class User: the role played by a DICOM Application Entity (DIMSE-Service-User) that invokes operations and performs notifications on a specific Association. + + Service Class Provider: the role played by a DICOM Application Entity (DIMSE-Service-User) that performs operations and invokes notifications on a specific Association. + + Service Class: a collection of SOP Classes and/or Meta SOP Classes that are related in that they are described together to accomplish a single application. + + Service-Object Pair (SOP) Class: the union of a specific set of DIMSE Services and one related Information Object Definition (as specified by a Service Class Definition) that completely defines a precise context for communication of operations on such an object or notifications about its state. + + Service-Object Pair (SOP) Instance: a concrete occurrence of an Information Object that is managed by a DICOM Application Entity and may be operated upon in a communication context defined by a specific set of DIMSE Services (on a network or interchange media). A SOP Instance is persistent beyond the context of its communication. + + True Enhanced Image Storage SOP Class: a modality-specific Enhanced Image Storage SOP Class that is defined by an IOD that defines modality-specific Functional Group Sequences, Attributes and sets of values, and is intended for creation by acquisition devices. +
+
+ Device Independent Pixel Values + This Part of the Standard makes use of the following terms defined in : + + + P-Value + + + PCS-Value + + +
+
+ HTTP Definitions + This Part of the Standard makes use of the following terms defined in IETF RFC7230: + + + Origin-Server + + + User-Agent + + +
+
+ + Symbols and Abbreviations + The following symbols and abbreviations are used in this Part of the DICOM Standard. + + ACR American College of Radiology + + ASCII American Standard Code for Information Interchange + + AE Application Entity + + ANSI American National Standards Institute + + CDS Clinical Decision Support + + CEN TC251 + Comité Européen de Normalisation - Technical Committee 251 - Medical Informatics + + Chest CAD Computer-Aided Detection and/or Computer-Aided Diagnosis for chest radiography + + DICOM Digital Imaging and Communications in Medicine + + DIMSE DICOM Message Service Element + + DIMSE-C DICOM Message Service Element-Composite + + DIMSE-N DICOM Message Service Element-Normalized + + HL7 Health Level 7 + + IE Information Entity + + IEEE Institute of Electrical and Electronics Engineers + + IOD Information Object Definition + + IS Information System + + ISO International Standards Organization + + JIRA Japan Medical Imaging and Radiological Systems Industries Association + + JPIP JPEG 2000 Interactive Protocol + + MAR Medication Administration Record + + NEMA National Electrical Manufacturers Association + + OSI Open Systems Interconnection + + SCP Service Class Provider + + SCU Service Class User + + SOP Service-Object Pair + + UID Unique Identifier + + + Conventions +
+ Entity-Relationship Model +
+ Entity + An entity is used in an Entity-Relationship (E-R) model to represent a Real-World Object, class of Real-World Objects, or DICOM data representation (such as IOD or Module). An entity is depicted as a box within this Part of the DICOM Standard as shown in Figure 5-1. + +
+ Entity Convention + + + + + + +
+
+
+
+ Relationship + A relationship, which defines how entities are related, is depicted as a diamond within this Standard as shown in Figure 5-2. + +
+ Relationship Convention + + + + + + +
+
+ The relationship is read from source to destination entity as indicated by the arrows. The a and b show the source and destination cardinality of the relationship respectively. The following cardinalities are permitted: + + + (a = 1, b = 1) -one source entity is related to one destination entity + + + (a = 1, b = 0-n) -one source entity is related to zero or more destination entities + + + (a = 1, b = 1-n) -one source entity is related to one or more destination entities + + + (a = 1-n, b = 1) -one or more source entities are related to one destination entity + + + (a = 1-n, b = 0-n) -one or more source entities are related to zero or more destination entities + + + (a = 1-n, b = 1-n) -one or more source entities are related to one or more destination entities + + + In a relationship where (a = 1-n, b = 1-n) the values of the source and destination cardinalities may be different. The value "n" simply denotes one or more. + + DICOM has added the use of arrows to the E-R diagramming conventions often used in other literature. This has been done to avoid the possibility of inferring an incorrect relationship that can result from reading a relationship in the reverse order of that intended. For example, a relationship "Cat Catches Mouse" could be read "Mouse Catches Cat" if the arrows were not present. + + A relationship may be bi-directional (i.e., the relationship is true in both directions). In such a case, the convention used is arrows pointing toward both the source and the destination entities. +
+
+
+ Sequences + Certain tables in this Part of the DICOM Standard denote a Sequence of Items by using the symbol: '>.' + In , '>' is used to identify a 'Sequence of Modules.' Nested Sequences of Modules are identified by '>>'. In and , '>' is used to identify a 'Sequence of Attributes'. See for the complete specification of how Sequences of Items shall be encoded. + + Information Object Definitions (IODs) that include the Sequence of Module construct are often called folders. The use of 'Sequences of Attributes' is not limited to 'Folders.' + +
+
+ Response Status Values + Certain tables in this Part of the DICOM Standard denote an implementation specific response status code by using the symbol: 'xx' as part of the code. +
+
+ Usage Specification + The building blocks of SOP Classes are Modules and DIMSE Services. The DIMSE Services associated with a SOP Class may be Mandatory (M) or Optional (U). The usage may be different for the SCU and SCP. The usage is specified as a pair of letters: the former indicating the SCU usage, the latter indicating the SCP usage. + The meaning and behavior of the usage specification for DIMSE Services are: + + + M/M + + The SCU shall support the DIMSE Service but is not required to use it on an Association. The SCP shall support the DIMSE Service. + + + + U/M + + The SCU may support and use the DIMSE Service. The SCP shall support the DIMSE Service. + + + + U/U + + The SCU may support and use the DIMSE Service. The SCP may support the DIMSE Service. If the SCP does not support the DIMSE Service used by the SCU, it shall return a Failure status. + + + + Modules and their usage in Composite IODs are defined in . Normalized IODs are also constructed from Modules but usage is specified on an Attribute basis in this Part of the DICOM Standard. The following usage specification applies to all Attributes of Normalized IODs unless superseded by a usage specification in a particular SOP Class Specification. + The meaning and behavior of the usage specification for Attributes of Normalized IODs are as follows: + + + 1/1 + + The SCU shall provide a value for the Attribute. If the SCU does not supply a value, the SCP shall return a Failure status ("Missing Attribute," code 0120H). The SCP shall support the Attribute. The SCP shall not support null values (Attribute provided with a zero length and no value) for the Attribute. + + + + 3/1 + + The SCU may retrieve or provide a value for the Attribute. The SCP shall support the Attribute. The SCP shall not support null values (Attribute provided with a zero length and no value) for the Attribute. + + + + -/1 + + The SCU's usage of the Attribute is undefined. The SCP shall support the Attribute. The SCP shall not support null values (Attribute provided with a zero length and no value) for the Attribute. + + + + 2/2 + + The SCU shall retrieve or provide a value for the Attribute. The SCU shall always provide the Attribute but a null value shall be permitted (Attribute provided with a zero length and no value). The SCP shall support the Attribute and permit null values (Attribute provided with a zero length and no value) for the Attribute. + + + + 3/2 + + The SCU may retrieve or provide a value for the Attribute. The SCP shall support the Attribute and permit null values (Attribute provided with a zero length and no value) for the Attribute. + + + + -/2 + + The SCU's usage of the Attribute is undefined. The SCP shall support the Attribute and permit null values (Attribute provided with a zero length and no value) for the Attribute. + + + + 3/3 + + The SCU may retrieve or provide a value for the Attribute. The SCP may support the Attribute. If the SCP does not support the Attribute and it is requested by the SCU, the SCP shall return either a Failure status ("Invalid Attribute Value", code 0106H) or a Warning status ("Attribute Value out of Range", code 0116H). If the SCU provides the Attribute and the SCP does not support the Attribute and returned a failure or warning, the Attribute shall be ignored. + + + + If the SCP usage type designation is modified by a "C" (e.g., 3/1C) the specification stated above shall be modified to include the requirement that the SCP shall support the Attribute if the specified condition is met. + For all N-CREATE, N-SET, N-GET, N-DELETE, N-ACTION and N-EVENT-REPORT operations, the SOP Class is conveyed in the request primitive in Affected SOP Class UID (0000,0002). The SOP Class UID (0008,0016) Attribute shall not be present in the Data Set. + For N-CREATE operations and N-EVENT-REPORT notifications, the SOP Instance is conveyed in Affected SOP Instance UID (0000,1000). The SOP Instance UID (0008,0018) Attribute shall not be present in the Data Set. + + In some Service Classes, the SOP Class definition may override the general provision in that allows the SOP Instance UID to be specified or omitted in the N-CREATE request primitive, and require that the SCU be responsible for specifying the SOP Instance UID. + + For N-SET, N-GET, N-ACTION and N-DELETE operations, the SOP Instance is conveyed in Requested SOP Instance UID (0000,1001). The SOP Instance UID (0008,0018) Attribute shall not be present in the data set. +
+
+ + DICOM Information Model + The DICOM Information Model defines the structure and organization of the information related to the communication of medical images. Figure 6-1 shows the relationships between the major structures of the DICOM Information Model. +
+ Information Object Definition + An Information Object Definition (IOD) is an object-oriented abstract data model used to specify information about Real-World Objects. An IOD provides communicating Application Entities with a common view of the information to be exchanged. + +
+ Major Structures of DICOM Information Model + + + + + + +
+
+ An IOD does not represent a specific instance of a Real-World Object, but rather a class of Real-World Objects that share the same properties. An IOD used to represent a single class of Real-World Objects is called a Normalized Information Object. An IOD that includes information about related Real-World Objects is called a Composite Information Object. +
+ Composite IOD + A Composite IOD is an Information Object Definition that represents parts of several entities in the DICOM Model of the Real-World. (see .) Such an IOD includes Attributes that are not inherent in the Real-World Object that the IOD represents but rather are inherent in related Real-World Objects. + These related Real-World Objects provide a complete context for the exchanged information. When an instance of a Composite IOD is communicated, this entire context is exchanged between Application Entities. Relationships between Composite IOD Instances shall be conveyed in this contextual information. + + + + Actual communication of IOD Instances is via SOP Instances. + + + Whenever Composite SOP Instances are in fact related, some of the contextual information is redundant (i.e., the same information about the same Real-World Objects is contained in multiple SOP Instances). + + + + The Composite IODs are specified in . +
+
+ Normalized IOD + A Normalized IOD is an Information Object Definition that generally represents a single entity in the DICOM Model of the Real-World. + When an instance of a Normalized IOD is communicated, the context for that instance is not actually exchanged. Instead, the context is provided through the use of pointers to related Normalized IOD Instances. + The Normalized IODs are specified in . +
+
+
+ Attributes + The Attributes of an IOD describe the properties of a Real-World Object Instance. Related Attributes are grouped into Modules that represents a higher level of semantics documented in the Module Specifications found in . + Attributes are encoded as Data Elements using the rules, the Value Representation and the Value Multiplicity concepts specified in . For specific Data Elements, the Value Representation and Value Multiplicity of Data Elements are specified in the Data Dictionary in . +
+
+ On-Line Communication and Media Storage Services + For on-line communication the DIMSE Services allow a DICOM Application Entity to invoke an operation or notification across a network or a point-to-point interface. DIMSE Services are defined in . + For media storage interchange, Media Storage Services allow a DICOM Application Entity to invoke media storage related operations. + Media Storage Services are discussed in . +
+ DIMSE-C Services + DIMSE-C Services are services applicable only to a Composite IOD. DIMSE-C provides only operation services. +
+
+ DIMSE-N Services + DIMSE-N Services are services applicable only to a Normalized IOD. DIMSE-N provides both operation and notification services. +
+
+
+ DIMSE Service Group + A DIMSE Service Group specifies one or more operations/notifications defined in that are applicable to an IOD. + DIMSE Service Groups are defined in this Part of the DICOM Standard, in the specification of a Service-Object Pair Class. +
+
+ Service-Object Pair (SOP) Class + A Service-Object Pair (SOP) Class is defined by the union of an IOD and a DIMSE Service Group. The SOP Class definition contains the rules and semantics that may restrict the use of the services in the DIMSE Service Group or the Attributes of the IOD. + The selection of SOP Classes is used by Application Entities to establish an agreed set of capabilities to support their interaction. This negotiation is performed at Association establishment time as described in . An extended negotiation allows Application Entities to further agree on specific options within a SOP Class. + + The SOP Class as defined in the DICOM Information Model is equivalent in ISO/OSI terminology to the Managed Object Class. Readers familiar with object oriented terminology will recognize the SOP Class operations (and notifications) as comprising the methods of an object class. + +
+ Normalized and Composite SOP Classes + DICOM defines two types of SOP Classes, Normalized and Composite. Normalized SOP Classes are defined as the union of a Normalized IOD and a set of DIMSE-N Services. Composite SOP Classes are defined as the union of a Composite IOD and a set of DIMSE-C Services. + + SOP Class Specifications play a central role for defining DICOM conformance requirements. It allows DICOM Application Entities to select a well-defined application level subset of this Standard to which they may claim conformance. See . + +
+
+
+ Association Negotiation + Association establishment is the first phase of communication between peer DICOM compliant Application Entities. The Application Entities shall use Association establishment to negotiate which SOP Classes can be exchanged and how this data will be encoded. + Association Negotiation is defined in . +
+
+ Service Class Specification + A Service Class Specification defines a group of one or more SOP Classes related to a specific function that is to be accomplished by communicating Application Entities. A Service Class Specification also defines rules that allow implementations to state some pre-defined level of conformance to one or more SOP Classes. Applications may conform to network SOP Classes as either a Service Class User (SCU) or Service Class Provider (SCP), and to media exchange SOP Classes as a File Set Creator (FSC), File Set Reader (FSR), or File Set Updater (FSU). + Service Class Specifications are defined in this Part of the DICOM Standard. + + Network interaction between peer Application Entities work on a 'client/server model.' The SCU acts as the 'client,' while the SCP acts as the 'server'. The SCU/SCP roles are determined during Association establishment. + +
+
+ + DICOM Model of the Real World + The DICOM view of the Real-World that identifies the relevant Real-World Objects and their relationships within the scope of the DICOM Standard is described in the DICOM Model of the Real-World Section of . + This section also describes the DICOM Information Model that identifies the various IODs specified by the DICOM Standard and their relationship. + + + Verification Service Class (Normative) +
+ Overview +
+ Scope + The Verification Service Class defines a service that verifies application level communication between peer DICOM AEs. This verification is accomplished on an established Association using the C-ECHO DIMSE-C service. +
+
+
+ SCU/SCP Behavior + A DICOM AE, supporting the Verification SOP Class SCU role, requests verification of communication to a remote DICOM AE. This request is performed using the C-ECHO request primitive. The remote DICOM AE, supporting the Verification SOP Class SCP role, issues an C-ECHO response primitive. Upon receipt of the C-ECHO confirmation, the SCU determines that verification is complete. See for the specification of the C-ECHO primitives. +
+
+ DIMSE-C Service Group + The C-ECHO DIMSE-C service shall be the mechanism used to verify communications between peer DICOM AEs. The C-ECHO service and protocol parameters shall be required as defined in . +
+
+ Verification SOP Class + The Verification SOP Class consists of the C-ECHO DIMSE-C service. No associated Information Object Definition is defined. The SOP Class UID shall be "1.2.840.10008.1.1". + No Specialized SOP Classes and/or Meta SOP Classes shall be defined for the Verification SOP Class. +
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The following negotiation rules apply to DICOM AEs that support the Verification SOP Class + + + The Association-requester (verification SCU role) in the A-ASSOCIATE request shall convey an Abstract Syntax, in a Presentation Context, for the Verification SOP Class. The Abstract Syntax Name shall be equivalent to the Verification SOP Class UID. + + + The Association-acceptor (verification SCP role) in the A-ASSOCIATE response shall accept the Abstract Syntax, in a Presentation Context, for the supported Verification SOP Class. + + + No Application Association Information specific to the Verification SOP Class shall be used. +
+
+ Conformance +
+ Conformance Supporting the SCU Role + Implementations that conform to the Verification SOP Class SCU role shall meet the: + + + C-ECHO service requirements as defined by the DIMSE Service Group, + + + + Association negotiation rules as defined in + + + +
+
+ Conformance Supporting the SCP Role + Implementations that conform to the Verification SOP Class SCP role shall meet the: + + + C-ECHO operation rules as defined by the DIMSE Service Group, + + + + Association negotiation rules as defined in + + + +
+
+ Conformance Statement + An implementation may conform to the Verification SOP Class as an SCU, SCP, or both. The Conformance Statement shall be in the format defined in . +
+
+
+ + Storage Service Class (Normative) +
+ Overview +
+ Scope + The Storage Service Class defines an application-level class-of-service that facilitates the simple transfer of information Instances (objects).. It allows one DICOM AE to send images, waveforms, reports, etc., to another. + Information Object Definitions for Instances that are transferred under the Storage Service Class shall adhere to the Composite Instance IOD Information Model specified in , and include at least the Patient, Study, and Series Information Entities. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Storage Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Storage Service Class are implemented using the C-STORE DIMSE-C service. C-STORE is described in . A successful completion of the C-STORE has the following semantics: + + + Both the SCU and the SCP support the type of information to be stored. + + + The information is stored in some medium. + + + For some time frame, the information may be accessed. + + + + + + Support for Storage SOP Classes does not necessarily involve support for SOP Classes of the Query/Retrieve Service Class. How the information may be accessed is implementation dependent. It is required that some access method exists. This method may require an implementation dependent operation at the SCP of the Storage Service Class. The duration of the storage is also implementation dependent, but is described in the Conformance Statement of the SCP. Storage SOP Classes are intended to be used in a variety of environments: e.g., for modalities to transfer images to workstations or archives, for archives to transfer images to workstations or back to modalities, for workstations to transfer processed images to archives, etc. + + + For the JPIP Referenced Pixel Data transfer syntaxes, transfers may result in storage of incomplete information in that the pixel data may be partially or completely transferred by some other mechanism at the discretion of the SCP. + + + +
+
+
+ Behavior + This Section discusses the SCU and SCP behavior for SOP Classes of the Storage Service Class. The C-STORE DIMSE-C Service shall be the mechanism used to transfer SOP Instances between peer DICOM AEs as described in . +
+ Behavior of an SCU + The SCU invokes a C-STORE DIMSE Service with a SOP Instance that meets the requirements of the corresponding IOD. The SCU shall recognize the status of the C-STORE service and take appropriate action upon the success or failure of the service. + + The appropriate action is implementation dependent. It is required that the SCU distinguish between successful and failed C-STORE responses. Appropriate action may differ according to application, but are described in the Conformance Statement of the SCU. + +
+
+ Behavior of an SCP + An SCP of a Storage SOP Class acts as a performing DIMSE-service-user for the C-STORE Service. By performing this service successfully, the SCP indicates that the SOP Instance has been successfully stored. +
+
+ Statuses + + defines the specific status code values that might be returned in a C-STORE response. General status code values and fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-STORE Status
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources + + A7xx + + (0000,0902) +
+ + Error: Data Set does not match SOP Class + + A9xx + + (0000,0901) + (0000,0902) +
+ + Error: Cannot understand + + Cxxx + + (0000,0901) + (0000,0902) +
+ Warning + + Coercion of Data Elements + + B000 + + (0000,0901) + (0000,0902) +
+ + Data Set does not match SOP Class + + B007 + + (0000,0901) + (0000,0902) +
+ + Elements Discarded + + B006 + + (0000,0901) + (0000,0902) +
+ Success + + + 0000 + + None +
+
+
+
+ Association Negotiation + SCUs and SCPs of Storage SOP Classes operate on SOP Instances specific to the SOP Class. They may use the SOP Class Extended Negotiation Sub-Item defined in . This Sub-Item allows DICOM AEs to exchange application information specific to SOP Class specifications. This is achieved by defining the Service-class-application-information field. + SCUs may use the SOP Class Common Extended Negotiation Sub-Item defined in . This Sub-Item allows DICOM AEs to exchange information about the nature of the SOP Classes. + The SOP Class Extended Negotiation Sub-Item and SOP Class Common Extended Negotiation Sub-Item negotiation is optional for storage based SOP Classes. + The following negotiation rules apply to all DICOM SOP Classes and Specialized SOP Classes of the Storage Service Class. + The Association-requester (Storage SCU role) in the A-ASSOCIATE request shall convey: + + + one Abstract Syntax, in a Presentation Context, for each supported SOP Class of the Storage Service Class + + + optionally, one SOP Class Extended Negotiation Sub-Item, for each supported SOP Class of the Storage Service Class + + + optionally, one SOP Class Common Extended Negotiation Sub-Item, for each supported SOP Class of the Storage Service Class + + + The Association-acceptor (Storage SCP role) in the A-ASSOCIATE request shall accept: + + + one Abstract Syntax, in a Presentation Context, for each supported SOP Class of the Storage Service Class + + + optionally, one SOP Class Extended Negotiation Sub-Item, for each supported SOP Class of the Storage Service Class + + +
+ Extended Negotiation + At the time of Association establishment implementations may exchange information about their respective capabilities, as described in and . SCUs and SCPs may use the SOP Class Extended Negotiation Sub-Item Structure as described in to exchange information about the level of conformance and options supported. SCUs may use the SOP Class Common Extended Negotiation Sub-Item defined in to exchange information about the nature of the SOP Classes. + Extended negotiation is optional. In the event that either the SCU or the SCP does not support extended negotiation, the defaults shall apply. +
+ Service-Class-Application-Information (A-ASSOCIATE-RQ) + The SOP Class Extended Negotiation Sub-item is made of a sequence of mandatory fields as defined by . shows the format of the Service-class-application-information field of the SOP Class Extended Negotiation Sub-Item for SOP Classes of the Storage Service Class in the A-ASSOCIATE-RQ. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Service-Class-Application-Information (A-ASSOCIATE-RQ)
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Level of support + + This byte field defines the supported storage level of the Association-requester. It shall be encoded as an unsigned binary integer and shall use one of the following values: + 0 - level 0 SCP + 1 - level 1 SCP + 2 - level 2 SCP + 3 - N/A Association-requester is SCU only + If extended negotiation is not supported, the default shall have a value of 3. +
+ 2 + + Reserved + + This reserved field shall be sent with a value 00H but not tested to this value when received. +
+ 3 + + Level of Digital Signature support + + A Level 2 SCP may further define its behavior in this byte field. + 0 - The signature level is unspecified, the AE is an SCU only, or the AE is not a level 2 SCP + 1 - signature level 1 + 2 - signature level 2 + 3 - signature level 3 + If extended negotiation is not supported, the default shall have a value of 0. +
+ 4 + + Reserved + + This reserved field shall be sent with a value 00H but not tested to this value when received. +
+ 5 + + Element Coercion + + This byte field defines whether the Association-requester may coerce Data Elements. It shall be encoded as an unsigned binary integer and shall use one of the following values: + 0 - does not coerce any Data Element + 1 - may coerce Data Elements + 2 - N/A - Association-requester is SCU only + If extended negotiation is not supported, the default shall have a value of 2. +
+ 6 + + Reserved + + This reserved field shall be sent with a value 00H but not tested to this value when received. +
+
+
+ Service-Class-Application-Information (A-ASSOCIATE-AC) + The SOP Class Extended Negotiation Sub-item is made of a sequence of mandatory fields as defined by . shows the format of the Service-class-application-information field of the SOP Class Extended Negotiation Sub-Item for SOP Classes of the Storage Service Class in the A-ASSOCIATE-AC. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Service-Class-Application-Information (A-ASSOCIATE-AC)
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Level of support + + This byte field defines the supported storage level of the Association-acceptor. It shall be encoded as an unsigned binary integer and shall use one of the following values: + 0 - level 0 SCP + 1 - level 1 SCP + 2 - level 2 SCP + 3 - N/A - Association-acceptor is SCU only + If extended negotiation is not supported, no assumptions shall be made by the Association-requester about the capabilities of the Association-acceptor based upon this extended negotiation. +
+ 2 + + Reserved + + This reserved field shall be sent with a value 00H but not tested to this value when received. +
+ 3 + + Level of Digital Signature support + + A Level 2 SCP may further define its behavior in this byte field. + 0 - The signature level is unspecified, the AE is an SCU only, or the AE is not a level 2 SCP + 1 - signature level 1 + 2 - signature level 2 + 3 - signature level 3 + If extended negotiation is not supported, no assumptions shall be made by the Association-requester about the capabilities of the Association-acceptor based upon this extended negotiation. +
+ 4 + + Reserved + + This reserved field shall be sent with a value 00H but not tested to this value when received. +
+ 5 + + Element Coercion + + This byte field defines whether the Association-acceptor may coerce Data Elements. It shall be encoded as an unsigned binary integer and shall use one of the following values: + 0 - does not coerce any Data Element + 1 - may coerce Data Elements + 2 - N/A - Association-acceptor is SCU only + If extended negotiation is not supported, no assumptions shall be made by the Association-requester about the capabilities of the Association-acceptor based upon this extended negotiation. +
+ 6 + + Reserved + + This reserved field shall be sent with a value 00H but not tested to this value when received. +
+
+
+ Service Class UID (A-ASSOCIATE-RQ) + SOP Class Common Extended Negotiation Sub-Item allows the SCU to convey the Service Class UID of each proposed SOP Class. + The Storage Service Class UID shall be "1.2.840.10008.4.2". +
+
+ Related General SOP Classes (A-ASSOCIATE-RQ) + A limited set of Standard SOP Classes in the Storage Service Class are defined to have one or more Related General SOP Classes. The Related General SOP Classes may be conveyed using the SOP Class Relationship Extended Negotiation during association establishment as defined in . identifies which Standard SOP Classes participate in this mechanism. If a Standard SOP Class is not listed in this table, Related General SOP Classes shall not be included in a Related Storage SOP Class Extended Negotiation Sub-Item. + + Implementation-defined Specialized SOP Classes (see ) of the Storage Service Class may convey a Related General SOP Class. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Standard and Related General SOP Classes
+ + SOP Class Name + + + + Related General SOP Class Name + +
+ 12-lead ECG Waveform Storage + + General ECG Waveform Storage +
+ Digital Mammography X-Ray Image Storage - For Presentation + + Digital X-Ray Image Storage - For Presentation +
+ Digital Mammography X-Ray Image Storage - For Processing + + Digital X-Ray Image Storage - For Processing +
+ Digital Intra-Oral X-Ray Image Storage - For Presentation + + Digital X-Ray Image Storage - For Presentation +
+ Digital Intra-Oral X-Ray Image Storage - For Processing + + Digital X-Ray Image Storage - For Processing +
+ Basic Text SR + + Enhanced SR +
+ Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Enhanced SR + + Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Comprehensive SR + + Comprehensive 3D SR +
+ Extensible SR +
+ Comprehensive 3D SR + + Extensible SR +
+ Procedure Log + + Enhanced SR +
+ Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Simplified Adult Echo SR + + Enhanced SR +
+ Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ X-Ray Radiation Dose SR + + Enhanced SR +
+ Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Radiopharmaceutical Radiation Dose SR + + Enhanced SR +
+ Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Patient Radiation Dose SR + + Enhanced SR +
+ Comprehensive SR +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Acquisition Context SR + + Enhanced SR (see note) +
+ Comprehensive SR (see note) +
+ Comprehensive 3D SR +
+ Extensible SR +
+ Spectacle Prescription Report + + Enhanced SR +
+ Macular Grid Thickness and Volume Report + + Enhanced SR +
+ Enhanced CT Image Storage + + Legacy Converted Enhanced CT Image Storage +
+ Enhanced MR Image Storage + + Legacy Converted Enhanced MR Image Storage +
+ Enhanced PET Image Storage + + Legacy Converted Enhanced PET Image Storage +
+ + The Acquisition Context SR may be encoded as Enhanced or Comprehensive only if it does not contain stereotactic coordinates (SCOORD3D). + +
+
+
+
+ Conformance + An implementation that conforms to Storage SOP Classes shall meet the: + + + C-STORE Service requirements as defined in + + + + Association requirements as defined in + + + + + No SCU or SCP behavior requirements other than those in this section are specified. In particular, an SCP of the Storage SOP Classes may not attach any significance to the particular association or associations over which C-STORE operations are requested, nor the order in which C-STORE operations occur within an association. No constraints are placed on the operations an SCU may perform during any particular association, other than those defined during association negotiation. An SCP may not expect an SCU to perform C-STORE operations in a particular order. + + Similarly, no semantics are attached to the closing of an Association, such as the end of a Study or Performed Procedure Step. +
+ Conformance as an SCP +
+ Levels of Conformance + Three levels of conformance to the Storage SOP Classes as an SCP may be provided: + + + Level 0 (Local). Level 0 conformance indicates that a user-defined subset of the Attributes of the image will be stored, and all others will be discarded. This subset of the Attributes shall be defined in the Conformance Statement of the implementer. + + + Level 1 (Base). Level 1 conformance indicates that all Type 1 and 2 Attributes defined in the IOD associated with the SOP Class will be stored, and may be accessed. All other elements may be discarded. The SCP may, but is not required to validate that the Attributes of the SOP Instance meets the requirements of the IOD. + + + Level 2 (Full). Level 2 conformance indicates that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition associated with the SOP Class, as well as any Standard Extended Attributes (including Private Attributes) included in the SOP Instance, will be stored and may be accessed. The SCP may, but is not required to validate that the Attributes of the SOP Instance meet the requirements of the IOD. + + + + A Level 2 SCP may discard (not store) Type 3 Attributes that are empty (zero length and no Value), since the meaning of an empty Type 3 Attribute is the same as absence of the Attribute. See definition of "Type 3 Optional Data Elements". + +
+
+ Support of Additional SOP Classes + An SCP that claims conformance to Level 2 (Full) support of the Storage Service Class may accept any Presentation Context negotiation of a SOP Class that specifies the Storage Service Class during the SOP Class Common Extended Negotiation (see ), without asserting conformance to that SOP Class in its Conformance Statement. + + + + The SCP may support storage of all SOP Classes of the Storage Service Class, preserving all Attributes as a Level 2 SCP. + + + This Extended Negotiation allows an SCP to determine that a private SOP Class (per ) in a proposed Presentation Context follows the semantics of the Storage Service Class, and may be handled accordingly. + + + + An SCP that claims conformance to Level 2 (Full) support of a Related General SOP Class may accept any Presentation Context negotiation of a SOP Class that specifies that Related General SOP Class during the SOP Class Common Extended Negotiation, without asserting conformance to that specialized SOP Class in its Conformance Statement. + + + + The term "specialized" in this section is used generically, including both Implementation-defined Specialized SOP Classes and Standard SOP Classes specified in . + + + The SCP may handle instances of such specialized SOP Classes using the semantics of the Related General SOP Class, but preserving all additional (potentially Type 1 or 2) Attributes as a Level 2 SCP. + + + An SCP that has access to the current content of might use that to determine acceptance of proposed Presentation Context SOP Classes. This allows an SCP, even without Extended Negotiation, to be able to identify all standard SOP Classes of the Storage Service Class. Access to may be through private means, or to the publication of PS3 on the web site of the DICOM Standards Committee. This provides an automated alternative to manually editing a table of supported Storage SOP Classes. + + + +
+
+ Coercion of Attributes + At any level of conformance, the SCP of the Storage Service Class may modify the values of certain Attributes in order to coerce the SOP Instance into the Query Model of the SCP. The Attributes that may be modified are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes Subject to Coercion
+ + Attribute Name + + + + Tag + +
+ Patient ID + + (0010,0020) +
+ Issuer of Patient ID + + (0010,0021) +
+ Other Patient IDs Sequence + + (0010,1002) +
+ Study Instance UID + + (0020,000D) +
+ Series Instance UID + + (0020,000E) +
+ The SCP of the Storage Service Class may modify the values of Code Sequence Attributes to convert from one coding scheme into another. This includes changing from deprecated values of Coding Scheme Designator (0008,0102) or Code Value (0008,0100) to currently valid values. + If an SCP performs such a modification, it shall return a C-STORE response with a status of Warning. + + + + Modification of these Attributes may be necessary if the SCP is also an SCP of a Query/Retrieve SOP Classes. These SOP Classes are described in this Standard. For example, an MR scanner may be implemented to generate Study Instance UIDs for images generated on the MR. When these images are sent to an archive that is HIS/RIS aware, it may choose to change the UID of the study assigned to the study by the PACS. The mechanism by which it performs this coercion is implementation dependent. + + + An SCP may, for instance, convert Coding Scheme Designator values "SNM3" to "SRT", in accordance with the DICOM conventions for SNOMED (see ). + + + Modification of Attributes that may be used to reference a SOP Instance by another SOP Instance (such as Study Instance UID and Series Instance UID Attributes) will make that reference invalid. Modification of these Attributes is strongly discouraged. + + + Other Attributes may be modified/corrected by an SCP of a Storage SOP Class. + + + Modification of Attributes may affect digital signatures referencing the content of the SOP Instance. + + + +
+
+ Levels of Digital Signature + Three levels of Digital Signature support are defined for an SCP that claims conformance to Level 2 (Full) storage support: + + + Signature Level 1. SCP may not preserve Digital Signatures and does not replace them. + + + Signature Level 2. SCP does not preserve the integrity of incoming Digital Signatures, but does validate the signatures of SOP Instances being stored, takes implementation-specific measures for insuring the integrity of data stored, and will add replacement Digital Signatures before sending SOP Instances elsewhere. + + + Signature Level 3. SCP does preserve the integrity of incoming Digital Signatures (i.e., is bit-preserving and stores and retrieves all Attributes regardless of whether they are defined in the IOD). + + +
+
+
+ Conformance as an SCU + The SCU shall generate only C-STORE requests with SOP Instances that meet the requirements of the IOD associated with the SOP Class. +
+ SCU Fall-Back Behavior + During Association Negotiation, an application may propose a specialized SOP Class and its related general SOP Class in separate Presentation Contexts as a Storage SCU. If the Association Acceptor rejects the specialized SOP Class Presentation Context, but accepts the related general SOP Class Presentation Context, the application may send instances of the specialized SOP Class as instances of the related general SOP Class. In this fall-back behavior, the SOP Class UID of the instance shall be the UID of the related general SOP Class, and any special semantics associated with the specialized SOP Class may be lost; the SOP Instance UID shall remain the same. + + The SCU may include the SOP Class UID of the original intended specialized SOP Class in the Attribute Original Specialized SOP Class UID (0008,001B) of the instance sent under the related general SOP Class. In some cases, e.g., when all intermediate storage applications are Level 2 SCPs, this may allow an ultimate receiver of the instance to recast it as an instance of the specialized SOP Class IOD. However, this transformation is not guaranteed. + +
+
+
+ Conformance Statement Requirements + An implementation may conform to a SOP Class of the Storage Service Class as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ Conformance Statement for an SCU + The following issues shall be documented in the Conformance Statement of any implementation claiming conformance to the Storage SOP Class as an SCU: + + + The behavior of the SCU in the case of a successful C-STORE response status shall be described. + + + The behavior of the SCU in each case of an unsuccessful C-STORE response status shall be described. + + + The behavior of the SCU in the case of a Warning status received in response to a C-STORE operation. + + + Whether extended negotiation is supported. + + + The optional elements that may be included in Storage SOP Instances for each IOD supported shall be listed. + + + The standard and privately defined Functional Groups that may be included in Storage SOP Instances for each Multi-frame IOD that support Functional Groups. + + + The behavior of the SCU in the case of a C-STORE operation using a referenced pixel data transfer syntax such as JPIP Referenced Pixel Data Transfer Syntax shall be described. This includes the duration of validity of the reference + + +
+
+ Conformance Statement for an SCP + The following issues shall be documented in the Conformance Statement of any implementation claiming conformance to the Storage Service Class as an SCP: + + + The level of conformance, as defined by , shall be stated. + + + The level of Digital Signature support, as defined by , shall be stated. + + + The optional elements that will be discarded (if any) shall be listed for each IOD supported. + + + The mechanisms by which additional SOP Classes are dynamically supported, as defined by , shall be stated. + + + The Conformance Statement shall document the policies concerning the Attribute Lossy Image Compression (0028,2110). + + + The behavior of the SCP in the case of a successful C-STORE operation shall be described. This includes the following: + + + the access method for a stored SOP Instance + + + the duration of the storage + + + + + The meaning of each case of an unsuccessful C-STORE response status shall be described, as well as appropriate recovery action. + + + The meaning of each case of a warning C-STORE response status shall be described, as well as appropriate action. + + + If the SCP performs coercion on any Attributes, this shall be stated, and the conditions under which it may occur shall be described. + + +
+
+
+ Specialized Conformance + Implementations may provide Specialized SOP Class conformance by providing a proper superset of the SOP Instances to be stored. Implementations providing Specialized SOP Class Conformance to one of the SOP Classes defined in this Annex shall be conformant as described in the following sections and shall include within their Conformance Statement information as described in the following sections. + An implementation shall be permitted to conform as a Specialization of the standard SOP Class as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ Specialized SOP Class Identification + Any implementation that specializes the standard SOP Class shall define its specialization as an Allomorphic subclass of the standard SOP Class. As such, the specialization shall have its own unique SOP Class identification. + The Conformance Statement shall include a SOP Class Identification Statement as defined in , declaring a SOP Name and SOP Class UID that identify the Specialized SOP Class. The SOP Name is not guaranteed to be unique (unless the implementer chooses to copyright it) but is provided for informal identification of the SOP Class. The SOP Class UID shall uniquely identify the Specialized SOP Class and conform to the DICOM UID requirements as specified in . +
+
+ Specialized Information Object Definition + The standard SOP Class may be specialized by supporting additional private Attributes. The SCU Operations Statement shall describe these specializations and be formatted as defined in . Following this statement shall be the list of Attributes that may be sent or stored with SOP Instances. +
+
+
+
+ Standard SOP Classes + The SOP Classes in the Storage Service Class identify the Composite IODs to be stored. identifies Standard SOP Classes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Standard SOP Classes
+ + SOP Class Name + + + + SOP Class UID + + + + IOD Specification (defined in ) + +
+ Computed Radiography Image Storage + + 1.2.840.10008.5.1.4.1.1.1 + + + + +
+ Digital X-Ray Image Storage - For Presentation + + 1.2.840.10008.5.1.4.1.1.1.1 + + + + + (see ) +
+ Digital X-Ray Image Storage - For Processing + + 1.2.840.10008.5.1.4.1.1.1.1.1 + + + + + (see ) +
+ Digital Mammography X-Ray Image Storage - For Presentation + + 1.2.840.10008.5.1.4.1.1.1.2 + + + + + (see ) +
+ Digital Mammography X-Ray Image Storage - For Processing + + 1.2.840.10008.5.1.4.1.1.1.2.1 + + + + + (see ) +
+ Digital Intra-Oral X-Ray Image Storage - For Presentation + + 1.2.840.10008.5.1.4.1.1.1.3 + + + + + (see ) +
+ Digital Intra-Oral X-Ray Image Storage - For Processing + + 1.2.840.10008.5.1.4.1.1.1.3.1 + + + + + (see ) +
+ CT Image Storage + + 1.2.840.10008.5.1.4.1.1.2 + + + + +
+ Enhanced CT Image Storage + + 1.2.840.10008.5.1.4.1.1.2.1 + + + + + (see ) +
+ Legacy Converted Enhanced CT Image Storage + + 1.2.840.10008.5.1.4.1.1.2.2 + + + + + (see ) +
+ Ultrasound Multi-frame Image Storage + + 1.2.840.10008.5.1.4.1.1.3.1 + + + + +
+ MR Image Storage + + 1.2.840.10008.5.1.4.1.1.4 + + + + +
+ Enhanced MR Image Storage + + 1.2.840.10008.5.1.4.1.1.4.1 + + + + + (see ) +
+ MR Spectroscopy Storage + + 1.2.840.10008.5.1.4.1.1.4.2 + + + + +
+ Enhanced MR Color Image Storage + + 1.2.840.10008.5.1.4.1.1.4.3 + + + + +
+ Legacy Converted Enhanced MR Image Storage + + 1.2.840.10008.5.1.4.1.1.4.4 + + + + + (see ) +
+ Ultrasound Image Storage + + 1.2.840.10008.5.1.4.1.1.6.1 + + + + +
+ Enhanced US Volume Storage + + 1.2.840.10008.5.1.4.1.1.6.2 + + + + +
+ Secondary Capture Image Storage + + 1.2.840.10008.5.1.4.1.1.7 + + + + +
+ Multi-frame Single Bit Secondary Capture Image Storage + + 1.2.840.10008.5.1.4.1.1.7.1 + + + + +
+ Multi-frame Grayscale Byte Secondary Capture Image Storage + + 1.2.840.10008.5.1.4.1.1.7.2 + + + + +
+ Multi-frame Grayscale Word Secondary Capture Image Storage + + 1.2.840.10008.5.1.4.1.1.7.3 + + + + +
+ Multi-frame True Color Secondary Capture Image Storage + + 1.2.840.10008.5.1.4.1.1.7.4 + + + + +
+ 12-lead ECG Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.1.1 + + + + +
+ General ECG Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.1.2 + + + + +
+ Ambulatory ECG Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.1.3 + + + + +
+ Hemodynamic Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.2.1 + + + + +
+ Cardiac Electrophysiology Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.3.1 + + + + +
+ Basic Voice Audio Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.4.1 + + + + +
+ General Audio Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.4.2 + + + + +
+ Arterial Pulse Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.5.1 + + + + +
+ Respiratory Waveform Storage + + 1.2.840.10008.5.1.4.1.1.9.6.1 + + + + +
+ Grayscale Softcopy Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.1 + + + + +
+ Color Softcopy Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.2 + + + + +
+ Pseudo-Color Softcopy Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.3 + + + + +
+ Blending Softcopy Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.4 + + + + +
+ XA/XRF Grayscale Softcopy Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.5 + + + + +
+ Grayscale Planar MPR Volumetric Presentation State Storage + + 1.2.840.10008.​5.​1.​4.​1.​1.​11.​6 + + + + +
+ Compositing Planar MPR Volumetric Presentation State Storage + + 1.2.840.10008.​5.​1.​4.​1.​1.​11.​7 + + + + +
+ Advanced Blending Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.8 + + + + +
+ Volume Rendering Volumetric Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.9 + + + + +
+ Segmented Volume Rendering Volumetric Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.10 + + + + +
+ Multiple Volume Rendering Volumetric Presentation State Storage + + 1.2.840.10008.5.1.4.1.1.11.11 + + + + +
+ X-Ray Angiographic Image Storage + + 1.2.840.10008.5.1.4.1.1.12.1 + + + + +
+ Enhanced XA Image Storage + + 1.2.840.10008.5.1.4.1.1.12.1.1 + + + + +
+ X-Ray Radiofluoroscopic Image Storage + + 1.2.840.10008.5.1.4.1.1.12.2 + + + + +
+ Enhanced XRF Image Storage + + 1.2.840.10008.5.1.4.1.1.12.2.1 + + + + +
+ X-Ray 3D Angiographic Image Storage + + 1.2.840.10008.5.1.4.1.1.13.1.1 + + + + +
+ X-Ray 3D Craniofacial Image Storage + + 1.2.840.10008.5.1.4.1.1.13.1.2 + + + + +
+ Breast Tomosynthesis Image Storage + + 1.2.840.10008.5.1.4.1.1.13.1.3 + + + + +
+ Breast Projection X-Ray Image Storage - For Presentation + + 1.2.840.10008.5.1.4.1.1.13.1.4 + + + + +
+ Breast Projection X-Ray Image Storage - For Processing + + 1.2.840.10008.5.1.4.1.1.13.1.5 + + + + +
+ Intravascular Optical Coherence Tomography Image Storage - For Presentation + + 1.2.840.10008.5.1.4.1.1.14.1 + + + + + (see ) +
+ Intravascular Optical Coherence Tomography Image Storage - For Processing + + 1.2.840.10008.5.1.4.1.1.14.2 + + + + + (see ) +
+ Nuclear Medicine Image Storage + + 1.2.840.10008.5.1.4.1.1.20 + + + + +
+ Parametric Map Storage + + 1.2.840.10008.5.1.4.1.1.30 + + + + +
+ Raw Data Storage + + 1.2.840.10008.5.1.4.1.1.66 + + + + +
+ Spatial Registration Storage + + 1.2.840.10008.5.1.4.1.1.66.1 + + + + +
+ Spatial Fiducials Storage + + 1.2.840.10008.5.1.4.1.1.66.2 + + + + +
+ Deformable Spatial Registration Storage + + 1.2.840.10008.5.1.4.1.1.66.3 + + + + +
+ Segmentation Storage + + 1.2.840.10008.5.1.4.1.1.66.4 + + + + +
+ Surface Segmentation Storage + + 1.2.840.10008.5.1.4.1.1.66.5 + + + + +
+ Tractography Results Storage + + 1.2.840.10008.5.1.4.1.1.66.6 + + + + +
+ Real World Value Mapping Storage + + 1.2.840.10008.5.1.4.1.1.67 + + + + +
+ Surface Scan Mesh Storage + + 1.2.840.10008.5.1.4.1.1.68.1 + + + + +
+ Surface Scan Point Cloud Storage + + 1.2.840.10008.5.1.4.1.1.68.2 + + + + +
+ VL Endoscopic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.1 + + + + +
+ Video Endoscopic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.1.1 + + + + +
+ VL Microscopic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.2 + + + + +
+ Video Microscopic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.2.1 + + + + +
+ VL Slide-Coordinates Microscopic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.3 + + + + +
+ VL Photographic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.4 + + + + +
+ Video Photographic Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.4.1 + + + + +
+ Ophthalmic Photography 8 Bit Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.1 + + + + +
+ Ophthalmic Photography 16 Bit Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.2 + + + + +
+ Stereometric Relationship Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.3 + + + + +
+ Ophthalmic Tomography Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.4 + + + + +
+ Wide Field Ophthalmic Photography Stereographic Projection Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.5 + + + + +
+ Wide Field Ophthalmic Photography 3D Coordinates Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.6 + + + + +
+ Ophthalmic Optical Coherence Tomography En Face Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.7 + + + + +
+ Ophthalmic Optical Coherence Tomography B-scan Volume Analysis Storage + + 1.2.840.10008.5.1.4.1.1.77.1.5.8 + + + + +
+ VL Whole Slide Microscopy Image Storage + + 1.2.840.10008.5.1.4.1.1.77.1.6 + + + + +
+ Lensometry Measurements Storage + + 1.2.840.10008.5.1.4.1.1.78.1 + + + + +
+ Autorefraction Measurements Storage + + 1.2.840.10008.5.1.4.1.1.78.2 + + + + +
+ Keratometry Measurements Storage + + 1.2.840.10008.5.1.4.1.1.78.3 + + + + +
+ Subjective Refraction Measurements Storage + + 1.2.840.10008.5.1.4.1.1.78.4 + + + + +
+ Visual Acuity Measurements Storage + + 1.2.840.10008.5.1.4.1.1.78.5 + + + + +
+ Spectacle Prescription Report Storage + + 1.2.840.10008.5.1.4.1.1.78.6 + + + + +
+ Ophthalmic Axial Measurements Storage + + 1.2.840.10008.5.1.4.1.1.78.7 + + + + +
+ Intraocular Lens Calculations Storage + + 1.2.840.10008.5.1.4.1.1.78.8 + + + + +
+ Macular Grid Thickness and Volume Report + + 1.2.840.10008.5.1.4.1.1.79.1 + + + + +
+ Ophthalmic Visual Field Static Perimetry Measurements Storage + + 1.2.840.10008.5.1.4.1.1.80.1 + + + + +
+ Ophthalmic Thickness Map Storage + + 1.2.840.10008.5.1.4.1.1.81.1 + + + + +
+ Corneal Topography Map Storage + + 1.2.840.10008.5.1.4.1.1.82.1 + + + + +
+ Basic Text SR Storage + + 1.2.840.10008.5.1.4.1.1.88.11 + + + + +
+ Enhanced SR Storage + + 1.2.840.10008.5.1.4.1.1.88.22 + + + + +
+ Comprehensive SR Storage + + 1.2.840.10008.5.1.4.1.1.88.33 + + + + +
+ Comprehensive 3D SR Storage + + 1.2.840.10008.5.1.4.1.1.88.34 + + + + +
+ Extensible SR Storage + + 1.2.840.10008.5.1.4.1.1.88.35 + + + + +
+ Procedure Log Storage + + 1.2.840.10008.5.1.4.1.1.88.40 + + + + +
+ Mammography CAD SR Storage + + 1.2.840.10008.5.1.4.1.1.88.50 + + + + +
+ Key Object Selection Storage + + 1.2.840.10008.5.1.4.1.1.88.59 + + + + +
+ Chest CAD SR Storage + + 1.2.840.10008.5.1.4.1.1.88.65 + + + + +
+ X-Ray Radiation Dose SR Storage + + 1.2.840.10008.5.1.4.1.1.88.67 + + + + +
+ Radiopharmaceutical Radiation Dose SR Storage + + 1.2.840.10008.5.1.4.1.1.88.68 + + + + +
+ Colon CAD SR Storage + + 1.2.840.10008.5.1.4.1.1.88.69 + + + + +
+ Implantation Plan SR Document Storage + + 1.2.840.10008.5.1.4.1.1.88.70 + + + + +
+ Acquisition Context SR Storage + + 1.2.840.10008.5.​1.​4.​1.​1.​88.​71 + + + + +
+ Simplified Adult Echo SR Storage + + 1.2.840.10008.5.​1.​4.​1.​1.​88.​72 + + + + +
+ Patient Radiation Dose SR Storage + + 1.2.840.10008.5.​1.​4.​1.​1.​88.​73 + + + + +
+ Content Assessment Results Storage + + 1.2.840.10008.5.1.4.1.1.90.1 + + +
+ Encapsulated PDF Storage + + 1.2.840.10008.5.1.4.1.1.104.1 + + + + +
+ Encapsulated CDA Storage + + 1.2.840.10008.5.1.4.1.1.104.2 + + + + +
+ Positron Emission Tomography Image Storage + + 1.2.840.10008.5.1.4.1.1.128 + + + + +
+ Enhanced PET Image Storage + + 1.2.840.10008.5.1.4.1.1.130 + + + + + (see ) +
+ Legacy Converted Enhanced PET Image Storage + + 1.2.840.10008.5.1.4.1.1.128.1 + + + + +
+ Basic Structured Display Storage + + 1.2.840.10008.5.1.4.1.1.131 + + + + +
+ CT Performed Procedure Protocol Storage + + 1.2.840.10008.5.1.4.1.1.200.2 + + + + +
+ RT Image Storage + + 1.2.840.10008.5.1.4.1.1.481.1 + + + + +
+ RT Dose Storage + + 1.2.840.10008.5.1.4.1.1.481.2 + + + + +
+ RT Structure Set Storage + + 1.2.840.10008.5.1.4.1.1.481.3 + + + + +
+ RT Beams Treatment Record Storage + + 1.2.840.10008.5.1.4.1.1.481.4 + + + + +
+ RT Plan Storage + + 1.2.840.10008.5.1.4.1.1.481.5 + + + + +
+ RT Brachy Treatment Record Storage + + 1.2.840.10008.5.1.4.1.1.481.6 + + + + +
+ RT Treatment Summary Record Storage + + 1.2.840.10008.5.1.4.1.1.481.7 + + + + +
+ RT Ion Plan Storage + + 1.2.840.10008.5.1.4.1.1.481.8 + + + + +
+ RT Ion Beams Treatment Record Storage + + 1.2.840.10008.5.1.4.1.1.481.9 + + + + +
+ RT Beams Delivery Instruction Storage + + 1.2.840.10008.5.1.4.34.7 + + + + +
+ RT Brachy Application Setup Delivery Instruction Storage + + 1.2.840.10008.5.1.4.34.10 + + + + +
+ + The Generic Implant Template Storage, Implant Assembly Template Storage, and Implant Template Group Storage SOP Classes were formerly specified in this table, incorrectly since they do not use the Patient / Study / Series / Instance information model. Those have been consolidated into the Non-Patient Object Storage Service Class (see ). + +
+ Specialization for Standard SOP Classes +
+ Digital X-Ray Image Storage SOP Classes + The Digital X-Ray Image Storage - For Presentation SOP Class shall use the DX IOD with an Enumerated Value of FOR PRESENTATION for Presentation Intent Type (0008,0068). + The Digital X-Ray Image Storage - For Processing SOP Class shall use the DX IOD with an Enumerated Value of FOR PROCESSING for Presentation Intent Type (0008,0068). + An SCU or SCP of the Digital X-Ray Image Storage - For Processing SOP Class shall also support the Digital X-Ray Image Storage - For Presentation SOP Class. + + + + The intent of this requirement is to ensure a useful level of interoperability by avoiding the situation where an SCU might support only the Digital X-Ray Image Storage - For Processing SOP Class and an SCP only the Digital X-Ray Image Storage - For Presentation SOP Class, or vice versa. The burden is therefore to support the Digital X-Ray Image Storage - For Presentation SOP Class as a "baseline". + + + The term "support" is used in this section in the sense that an SCU or SCP must be capable of sending or receiving the For Presentation SOP Class. There is no intent to imply that an SCU must always send an instance of the For Presentation SOP Class when an instance of the For Processing SOP Class is sent. + Nor is there any intent to imply that during Association establishment, that a Presentation Context for the For Presentation SOP Class has to be proposed by the initiator. However, an association acceptor may reject a For Presentation SOP Class Presentation Context if it accepts a For Processing SOP Class Presentation Context, and prefers that SOP Class, in which case it may no longer be able to "pass on" the object later as an SCU unless it is able to generate a For Presentation object. + It is not possible for an SCP to determine from proposed Presentation Contexts whether or not an SCU "supports" (is capable of sending) both For Processing and For Presentation SOP Class Instances. Such a determination requires a priori knowledge of the information contained in the Conformance Statement for the SCU, as well as how the SCU is configured and operated. An SCU that supports both SOP Classes may well choose to only propose one or the other during Association establishment, depending on which Instances it actually intends to send over that particular association (although the SCU must be capable of sending instances of the For Presentation SOP Class if the SCP does not accept the For Processing). + The intent of the requirement is that if an SCU is only capable of sending the For Presentation SOP Class, any SCP will be guaranteed to be able to receive it. Conversely, if an SCP is only capable of receiving the For Presentation SOP Class, any SCU will be guaranteed to be able to send it. + + + +
+
+ Digital Mammography X-Ray Image Storage SOP Classes + The Digital Mammography X-Ray Image Storage - For Presentation SOP Class shall use the Digital Mammography IOD with an Enumerated Value of FOR PRESENTATION for Presentation Intent Type (0008,0068). + The Digital Mammography X-Ray Image Storage - For Processing SOP Class shall use the Digital Mammography IOD with an Enumerated Value of FOR PROCESSING for Presentation Intent Type (0008,0068). + An SCU or SCP of the Digital Mammography X-Ray Image Storage - For Processing SOP Class shall also support the Digital Mammography X-Ray Image Storage - For Presentation SOP Class. +
+
+ Digital Intra-Oral X-Ray Image Storage SOP Classes + The Digital Intra-Oral X-Ray Image Storage - For Presentation SOP Class shall use the Digital Intra-Oral X-Ray IOD with an Enumerated Value of FOR PRESENTATION for Presentation Intent Type (0008,0068). + The Digital Intra-Oral X-Ray Image Storage - For Processing SOP Class shall use the Digital Intra-Oral X-Ray IOD with an Enumerated Value of FOR PROCESSING for Presentation Intent Type (0008,0068). + An SCU or SCP of the Digital Intra-Oral X-Ray Image Storage - For Processing SOP Class shall also support the Digital Intra-Oral X-Ray Image Storage - For Presentation SOP Class. +
+
+ Softcopy Presentation State Storage SOP Classes + See . +
+
+ Structured Reporting Storage SOP Classes + The requirements of apply to the following SOP Classes: + + + Basic Text SR + + + Extensible SR, Enhanced SR, and SOP Classes for which it is the Related General SOP Class + + + Comprehensive 3D SR, Comprehensive SR, and SOP Classes for which they are the Related General SOP Classes + + + Mammography CAD SR + + + Chest CAD SR + + + Procedure Log + + + X-Ray Radiation Dose SR + + + Radiopharmaceutical Radiation Dose SR + + + Patient Radiation Dose SR + + + Spectacle Prescription Report + + + Colon CAD SR + + + Macular Grid Thickness and Volume Report + + + Implantation Plan SR Document + + + Acquisition Context SR + + + Simplified Adult Echo SR + + + + requirements do not apply to the Key Object Selection Document SOP Class. +
+
+ Enhanced MR Image Storage and Legacy Converted Enhanced MR Image Storage SOP Class + An SCP of the Enhanced MR Image Storage or Legacy Converted Enhanced MR Image Storage SOP Class shall also support the Grayscale Softcopy Presentation State Storage SOP Class. + + This requirement is present in order to allow the exchange of graphical annotations created by an acquisition or conversion device. + +
+
+ Enhanced CT Image Storage and Legacy Converted Enhanced CT Image Storage SOP Class + An SCP of the Enhanced CT Image Storage or Legacy Converted Enhanced CT Image Storage SOP Class shall also support the Grayscale Softcopy Presentation State Storage SOP Class. + + This requirement is present in order to allow the exchange of graphical annotations created by an acquisition or conversion device. + +
+
+ Enhanced MR Color Image Storage SOP Class + An SCP of the Enhanced MR Color Image Storage SOP Class shall also support the Color Softcopy Presentation State Storage SOP Class. + + This requirement is present in order to allow the exchange of graphical annotations created by an acquisition device. + +
+
+ Basic Structured Display + An SCU of the Basic Structured Display Storage SOP Class that creates SOP Instances of the Class shall identify in its Conformance Statement the Composite Storage SOP Classes and Softcopy Presentation State Storage SOP Classes that are also supported by the SCU, and may be referenced by Basic Structured Display SOP Instances it creates. It shall identify in its Conformance Statement the values it may use in the Attributes Image Box Layout Type (0072,0304) and Type of Synchronization (0072,0434). + An SCP of the Basic Structured Display Storage SOP Class, when rendering SOP Instances of the Class, shall preserve the aspect ratio specified by the Nominal Screen Definition Sequence (0072,0102) Attributes Number of Vertical Pixels (0072,0104) and Number of Horizontal Pixels (0072,0106) without clipping. + + + + The SCP is not required to display using the exact number of vertical and horizontal pixels. The SCP may use as much of its display screen as it desires, while maintaining the Structured Display aspect ratio. + + + If the display screen has a different aspect ratio, the positioning of the display on the screen is unspecified (centered, left or right justified, top or bottom justified). + + + + An SCP of the Basic Structured Display Storage SOP Class that is capable of rendering SOP Instances of the Class shall identify in its Conformance Statement the Composite Storage SOP Classes and Softcopy Presentation State Storage SOP Classes that are also supported by the SCP, and will be rendered when referenced by Basic Structured Display SOP Instances for display. It shall specify in its Conformance Statement the user display controls and interactions for the values of Image Box Layout Type (0072,0304) and Type of Synchronization (0072,0434) that it supports. It shall identify in its Conformance Statement its behavior when encountering a referenced Presentation State or other Composite Storage SOP Instance whose display it does not support, or an unsupported value of Image Box Layout Type or Type of Synchronization; such behavior shall include at a minimum a display to the user of the nature of the + incompatibility. +
+
+ Implant Template Storage SOP Classes + See . + + The requirements of this section have been consolidated into the Non-Patient Object Storage Service Class (see ). + +
+
+ Ophthalmic Axial Measurements Storage SOP Class + Ophthalmic axial measurements devices are used in the preoperative assessment of every cataract surgery patient. Ophthalmic axial measurements SOP Classes support ophthalmic axial measurements devices. + For a device that is both a SCU and a SCP of the Ophthalmic Axial Measurements Storage SOP Class, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for Ophthalmic Axial Measurements Storage SOP Classes: + + + A SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ IOL Calculation Storage SOP Class + IOL (intraocular lens) calculation is used in the preoperative assessment of every cataract surgery patient. IOL Calculation SOP Classes support IOL calculation software, which may be located either on ophthalmic axial measurement devices or on a separate computer. + For a device that is both a SCU and a SCP of the IOL Calculation Storage SOP Class, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for IOL Calculation Storage SOP Classes: + + + A SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ Intravascular OCT Image Storage SOP Classes + The Intravascular OCT Image Storage - For Presentation SOP Class shall use the IVOCT IOD with an Enumerated Value of FOR PRESENTATION for Presentation Intent Type (0008,0068). + The Intravascular OCT Image Storage - For Processing SOP Class shall use the IVOCT IOD with an Enumerated Value of FOR PROCESSING for Presentation Intent Type (0008,0068). + An SCU or SCP of the Intravascular OCT Image Storage - For Processing SOP Class shall also support the Intravascular OCT Image Storage - For Presentation SOP Class. + + + + The intent of this requirement is to ensure a useful level of interoperability by avoiding the situation where an SCU might support only the Intravascular OCT Image Storage - For Processing SOP Class and an SCP only the Intravascular OCT Image Storage - For Presentation SOP Class, or vice versa. The burden is therefore to support the Intravascular OCT Image Storage - For Presentation SOP Class as a "baseline". + + + The term "support" is used in this section in the sense that an SCU or SCP must be capable of sending or receiving the For Presentation SOP Class. There is no intent to imply that an SCU must always send an instance of the For Presentation SOP Class when an instance of the For Processing SOP Class is sent. + Nor is there any intent to imply that during Association establishment, that a Presentation Context for the For Presentation SOP Class has to be proposed by the initiator. However, an association acceptor may reject a For Presentation SOP Class Presentation Context if it accepts a For Processing SOP Class Presentation Context, and prefers that SOP Class, in which case it may no longer be able to "pass on" the object later as an SCU unless it is able to generate a For Presentation object. + It is not possible for an SCP to determine from proposed Presentation Contexts whether or not an SCU "supports" (is capable of sending) both For Processing and For Presentation SOP Class Instances. Such a determination requires a priori knowledge of the information contained in the Conformance Statement for the SCU, as well as how the SCU is configured and operated. An SCU that supports both SOP Classes may well choose to only propose one or the other during Association establishment, depending on which Instances it actually intends to send over that particular association (although the SCU must be capable of sending instances of the For Presentation SOP Class if the SCP does not accept the For Processing). + The intent of the requirement is that if an SCU is only capable of sending the For Presentation SOP Class, any SCP will be guaranteed to be able to receive it. Conversely, if an SCP is only capable of receiving the For Presentation SOP Class, any SCU will be guaranteed to be able to send it. + + + +
+
+ Ophthalmic Thickness Map Storage SOP Class + The Ophthalmic Thickness Map SOP Class encodes a topographic representation of the thickness/height measurements of the posterior eye. + For a device that is both a SCU and a SCP of the Ophthalmic Thickness Map Storage SOP Class, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for Ophthalmic Thickness Map Storage SOP Classes: + + + A SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ Enhanced PET Image Storage and Legacy Converted Enhanced PET Image Storage SOP Class + An SCP of the Enhanced PET Image Storage or Legacy Converted Enhanced PET Image Storage SOP Class shall also support the Grayscale Softcopy Presentation State Storage SOP Class. + + This requirement is present in order to allow the exchange of graphical annotations created by an acquisition or conversion device. + +
+
+ Enhanced PET Image Storage SOP Classes + An SCP of the Enhanced PET Image Storage SOP Class shall also support the Grayscale Softcopy Presentation State Storage SOP Class. + + This requirement is present in order to allow the exchange of graphical annotations created by an acquisition device. + +
+
+ Corneal Topography Map Storage SOP Class + The Corneal Topography Map SOP Class encodes a topographic representation of the curvature and/or elevation measurements of corneal anterior and posterior surfaces (e.g., maps that display corneal curvatures, corneal elevations, and corneal power, etc.). + For a device that is both a SCU and a SCP of the Corneal Topography Map Storage SOP Class, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for Corneal Topography Map Storage SOP Classes: + + + A SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ Breast Projection X-Ray Image Storage SOP Classes + The Breast Projection X-Ray Image Storage - For Presentation SOP Class shall use the Breast Projection X-Ray Image IOD with an Enumerated Value of FOR PRESENTATION for Presentation Intent Type (0008,0068). + The Breast Projection X-Ray Image Storage - For Processing SOP Class shall use the Breast Projection X-Ray Image IOD with an Enumerated Value of FOR PROCESSING for Presentation Intent Type (0008,0068). + An SCU or SCP of the Breast Projection X-Ray Image Storage - For Processing SOP Class shall also support the Breast Projection X-Ray Image Storage - For Presentation SOP Class. +
+
+ Planar MPR Volumetric Presentation State Storage SOP Classes + The requirements of apply to the following SOP Classes: + + + Grayscale Planar MPR Volumetric Presentation State Storage + + + Compositing Planar MPR Volumetric Presentation State Storage + + + The Grayscale Planar MPR Volumetric Presentation State Storage SOP Class shall use the with an Enumerated Value of MONOCHROME for Pixel Presentation (0008,9205) and shall have only a single item in the Volumetric Presentation State Input Sequence (0070,1201). + The Compositing Planar MPR Volumetric Presentation State Storage SOP Class shall use the with an Enumerated Value of TRUE COLOR for Pixel Presentation (0008,9205). +
+
+ Content Assessment Results Storage SOP Classes + An SCU of the Content Assessment Results Storage SOP Class that creates SOP Instances of the Class shall identify in its Conformance Statement the criteria for setting the Observation Significance (0082,0008). +
+
+ CT Performed Procedure Protocol Storage SOP Class + The CT Performed Procedure Protocol Storage SOP Class encodes the acquisition and reconstruction protocol parameter values used during a specific performed CT procedure and related details. + For a device that is both a SCU and a SCP of the CT Performed Procedure Protocol Storage SOP Class, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for CT Performed Procedure Protocol Storage SOP Classes: + + + A SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ Raw Data Storage SOP Class + For a device that is both a SCU and a SCP of the Raw Data Storage SOP Class, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for the Raw Data Storage SOP Class: + + + An SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ Enhanced Multi-Frame Image SOP Classes + An SCP of any of the Enhanced Multi-Frame Image SOP Classes that makes SOP Instances available through the Enhanced Multi-Frame Image Conversion Extended Negotiation of the Query/Retrieve Service Class (see ) shall provide Level 2 (Full) Storage SCP Conformance. + + Effective use of the Image Conversion option requires the storage of Type 3 Attributes. + +
+
+ Volume Rendering Volumetric Presentation State Storage SOP Classes + The requirements of apply to the following SOP Classes: + + + Volume Rendering Volumetric Presentation State SOP Class + + + Segmented Volume Rendering Volumetric Presentation State SOP Class + + + Multiple Volume Rendering Volumetric Presentation State SOP Class + + + The Volume Rendering Volumetric Presentation State Storage SOP Class shall use the and include a single item in Volumetric Presentation State Input Sequence (0070,1201) and a single item in Volume Stream Sequence (0070,1A08). Also, the value of Crop (0070,1204) shall be NO. + The Segmented Volume Rendering Volumetric Presentation State Storage SOP Class shall use the and include a single item in Volume Stream Sequence (0070,1A08). + The Multiple Volume Rendering Volumetric Presentation State Storage SOP Class shall use the and include two or more items in Volume Stream Sequence (0070,1A08). +
+
+
+
+ Retired Standard SOP Classes + The SOP Classes in were defined in previous versions of the DICOM Standard. They are now retired and have been replaced by new standard SOP Classes shown in . + + Usage of the retired SOP Classes is permitted by DICOM. However, new implementations are strongly encouraged to implement the newer SOP Classes. + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Retired Standard SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Nuclear Medicine Image Storage + + 1.2.840.10008.5.1.4.1.1.5 +
+ Ultrasound Image Storage + + 1.2.840.10008.5.1.4.1.1.6 +
+ Ultrasound Multi-frame Image Storage + + 1.2.840.10008.5.1.4.1.1.3 +
+ X-Ray Angiographic Bi-plane Image Storage + + 1.2.840.10008.5.1.4.1.1.12.3 +
+
+
+ + Query/Retrieve Service Class (Normative) +
+ Overview +
+ Scope + The Query/Retrieve Service Class defines an application-level class-of-service that facilitates the simple management of Composite Object Instances in a manner functionally similar to ACR-NEMA 300-1988. The types of queries that are allowed are not complex. This Service Class is not intended to provide a comprehensive generalized database query mechanism such as SQL. Instead, the Query/Retrieve Service Class is focused towards basic Composite Object Instance information queries using a small set of common Key Attributes. + In addition, the Query/Retrieve Service Class provides the ability to retrieve/transfer a well-identified set of Composite Object Instances. The retrieve/transfer capability allows a DICOM AE to retrieve Composite Object Instances from a remote DICOM AE or request the remote DICOM AE to initiate a transfer of Composite Object Instances to another DICOM AE. + + Functional similarity to ACR-NEMA 300-1988 facilitates the migration to DICOM. + + An Enhanced Multi-Frame Image Conversion Extended Negotiation option allows the Query/Retrieve Service Class to access Classic single-frame images that have been converted to Enhanced multi-frame images, or vice-versa. This is achieved by providing alternative "views" of studies, such that: + + + the default view provides the images in the form they were received, + + + a Classic single-frame "view" provides images as Classic single frame (that were received that way or have been converted from Enhanced multi-frame), + + + an Enhanced multi-frame "view" provides images as Enhanced multi-frame (that were received that way or have been converted to Enhanced multi-frame). + + + A query or retrieval above the IMAGE level does not show or return duplicate information (two sets of images). The SCU may request the default, enhanced multi-frame or Classic single frame view. For each view, referential integrity is required to be consistent within the scope of the Patient and that view; i.e., references to UIDs will be converted in all Instances, not only within converted images. + + + + The Classic single-frame view is not intended as an alternative to the Frame Level Retrieve SOP Classes defined in . Enhanced Image Storage SOP Classes and Frame Level Retrieve SOP Classes should be used together since they support a unified view of the relationships between instances through a common set of UIDs. + + + In the Enhanced view, Instances that have no Enhanced equivalent will be returned in their original form but with referential integrity related changes. + + + +
+
+ Conventions + The following conventions are used to define the types of keys used in Query/Retrieve Information Models. + + + + + + + + + + + + + + + + + + + + + + +
Key Type Conventions for Query/Retrieve Information Models
+ + Symbol + + + + Description + +
+ U + + Unique Key Attribute +
+ R + + Required Key Attribute +
+ O + + Optional Key Attribute +
+
+
+ Query/Retrieve Information Model + In order to serve as an SCP of the Query/Retrieve Service Class, a DICOM AE possesses information about the Attributes of a number of stored Composite Object Instances. This information is organized into a well defined Query/Retrieve Information Model. The Query/Retrieve Information Model shall be a standard Query/Retrieve Information Model, as defined in this Annex of the DICOM Standard. + Queries and Retrievals are implemented against well defined Information Models. A specific SOP Class of the Query/Retrieve Service Class consists of an Information Model Definition and a DIMSE-C Service Group. In this Service Class, the Information Model plays a role similar to an Information Object Definition (IOD) of most other DICOM Service Classes. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Query/Retrieve Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Query/Retrieve Service Class are implemented using the DIMSE-C C-FIND, C-MOVE, and C-GET services as defined in . + Both a baseline and extended behavior is defined for the DIMSE-C C-FIND, C-MOVE, and C-GET services. Baseline behavior specifies a minimum level of conformance for all implementations to facilitate interoperability. Extended behavior enhances the baseline behavior to provide additional features that may be negotiated independently at Association establishment time. + The following descriptions of the DIMSE-C C-FIND, C-MOVE, and C-GET services provide a brief overview of the SCU/SCP semantics: + + + A C-FIND service conveys the following semantics: + + + The SCU requests that the SCP perform a match of all the keys specified in the Identifier of the request, against the information it possesses, to the level (E.g. Patient, Study, Series, or Composite Object Instance) specified in the request. + + In this Annex, the term "Identifier" refers to the Identifier service parameter of the C-FIND, C-MOVE, or C-GET service as defined in . + + + + The SCP generates a C-FIND response for each match with an Identifier containing the values of all key fields and all known Attributes requested. All such responses will contain a status of Pending. A status of Pending indicates that the process of matching is not complete. + + + When the process of matching is complete a C-FIND response is sent with a status of Success and no Identifier. + + + A Refused or Failed response to a C-FIND request indicates that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-FIND-CANCEL request at any time during the processing of the C-FIND service. The SCP will interrupt all matching and return a status of Canceled. + + + + + A C-MOVE service conveys the following semantics: + + + The SCU supplies Unique Key values to identify an entity at the level of the retrieval. The SCP of the C-MOVE initiates C-STORE sub-operations for the corresponding storage SOP Instances identified by Unique Key values. These C-STORE sub-operations occur on a different Association than the C-MOVE service. The SCP role of the Query/Retrieve SOP Class and the SCU role of the Storage SOP Class may be performed by different applications that may or may not reside on the same system. Initiation mechanism of C-STORE sub-operations is outside of the scope of DICOM standard. + + This does not imply that they use the same AE Title. See and for the requirements to the C-MOVE SCP conformance. + + + + The SCP may optionally generate responses to the C-MOVE with status equal to Pending during the processing of the C-STORE sub-operations. These C-MOVE responses indicate the number of Remaining C-STORE sub-operations and the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. + + + When the number of Remaining C-STORE sub-operations reaches zero, the SCP generates a final response with a status equal to Success, Warning, Failed, or Refused. This response may indicate the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. If the status of a C-STORE sub-operation was Failed a UID List will be returned. + + + The SCU may cancel the C-MOVE service by issuing a C-MOVE-CANCEL request at any time during the processing of the C-MOVE. The SCP terminates all incomplete C-STORE sub-operations and returns a status of Canceled. + + + + + A C-GET service conveys the following semantics: + + + The SCU supplies Unique Key values to identify an entity at the level of the retrieval. The SCP generates C-STORE sub-operations for the corresponding storage SOP Instances identified by the Unique Key values. These C-STORE sub-operations occur on the same Association as the C-GET service and the SCU/SCP roles will be reversed for the C-STORE. + + + The SCP may optionally generate responses to the C-GET with status equal to Pending during the processing of the C-STORE sub-operations. These C-GET responses indicate the number of Remaining C-STORE sub-operations and the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. + + + When the number of Remaining C-STORE sub-operations reaches zero, the SCP generates a final response with a status equal to Success, Warning, Failed, or Refused. This response may indicate the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. If the status of a C-STORE sub-operation was Failed a UID List will be returned. + + + The SCU may cancel the C-GET service by issuing a C-GET-CANCEL request at any time during the processing of the C-GET. The SCP terminates all incomplete C-STORE sub-operations and returns a status of Canceled. + + + + +
+
+
+ Query/Retrieve Information Model Definition + The Query/Retrieve Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + + This SOP Class identifies the class of the Query/Retrieve Information Model (i.e., not the SOP Class of the stored SOP Instances for which the SCP has information). + + Information Model Definitions for standard SOP Classes of the Query/Retrieve Service Class are defined in this Annex. A Query/Retrieve Information Model Definition contains: + + + Entity-Relationship Model Definition + + + Key Attributes Definition + + +
+ Entity-Relationship Model Definition + For any Query/Retrieve Information Model, an Entity-Relationship Model defines a hierarchy of entities, with Attributes defined for each level in the hierarchy (e.g., Patient, Study, Series, Composite Object Instance). +
+
+ Attributes Definition + Attributes shall be defined at each level in the Entity-Relationship Model. An Identifier in a C-FIND, C-MOVE, or C-GET command shall contain values to be matched against the Attributes of the Entities in a Query/Retrieve Information Model. For any query, the set of entities for which Attributes are returned, shall be determined by the set of Key Attributes specified in the Identifier that have corresponding matches on entities managed by the SCP associated with the query. +
+ Attribute Types + All Attributes of entities in a Query/Retrieve Information Model shall be either a Unique Key, Required Key, or Optional Key. The term Key Attributes refers to Unique, Required, and Optional Key Attributes. +
+ Unique Keys + At each level in the Entity-Relationship Model, one Attribute shall be defined as a Unique Key. A single value in a Unique Key Attribute shall uniquely identify a single entity at a given level. That is, two entities at the same level may not have the same Unique Key value. + C-FIND, C-MOVE, and C-GET SCPs shall support existence and matching of all Unique Keys defined by a Query/Retrieve Information Model. All entities managed by C-FIND, C-MOVE, and C-GET SCPs shall have a specific non-zero length Unique Key value. + Unique Keys may be contained in the Identifier of a C-FIND request. Unique Keys shall be contained in the Identifier of C-MOVE and C-GET requests. +
+
+ Required Keys + At each level in the Entity-Relationship Model, a set of Attributes shall be defined as Required Keys. Required Keys imply the SCP of a C-FIND shall support matching based on a value contained in a Required Key of the C-FIND request. Multiple entities may have the same value for Required Keys. That is, a distinct value in a Required Key shall not necessarily identify a single entity at the level of the key. + C-FIND SCPs shall support existence and matching of all Required Keys defined by a Query/Retrieve Information Model. If a C-FIND SCP manages an entity with a Required Key of zero length, the value is considered unknown and all matching against the zero length Required Key shall be considered a successful match. + Required Keys may be contained in the Identifier of a C-FIND request. Required Keys shall not be contained in the Identifier of C-MOVE and C-GET requests. +
+
+ Optional Keys + At each level in the Entity-Relationship Model, a set of Attributes shall be defined as Optional Keys. + Optional Keys contained in the Identifier of a C-FIND request may have three different types of behavior depending on support for existence and/or matching by the C-FIND SCP. If the C-FIND SCP: + + + does not support the existence of the Optional Key, then the Attribute shall not be returned in C-FIND responses + + + supports the existence of the Optional Key but does not support matching on the Optional Key, then the Optional Key shall be processed in the same manner as a zero length Required Key. That is, the value specified to be matched for the Optional Key is ignored but a value may be returned by the SCP for this Optional Key. + + + supports the existence and matching of the Optional Key, then the Optional Key shall be processed in the same manner as a Required Key. + + + + + + C-FIND SCU may not assume an Optional Key with non-zero length will be processed in the same manner as a Required Key. The Conformance Statement of the C-FIND SCP shall list the Optional Keys that are supported. + + + Optional Keys are differentiated from Required Keys in that Optional Keys may or may not be supported for existence and/or matching by C-FIND SCPs. Whereas, Required Keys must always be supported by C-FIND SCPs. + + + + Optional Keys may be contained in the Identifier of a C-FIND request. Optional Keys shall not be contained in the Identifier of C-MOVE and C-GET requests. +
+
+
+ Attribute Matching + The following types of matching may be performed on Key Attributes in the Query/Retrieve Service Class: + + + Single Value Matching + + + List of UID Matching + + + Universal Matching + + + Wild Card Matching + + + Range Matching + + + Sequence Matching + + + Matching requires special characters (i.e., "*","?","-", "=" and "\"), which need not be part of the character repertoire for the VR of the Key Attributes. + + + + For example, the "-" character is not valid for the DA, DT and TM VRs but is used for range matching. + + + When character sets other than the default character repertoire are used, then the rules in apply, such as with respect to the use of the 05/12 "\" (BACKSLASH) (in ISO IR 6) or 05/12 "¥" (YEN SIGN) (in ISO IR 14). + + + + The total length of the Key Attribute may exceed the length as specified in the VR in . The Value Multiplicity (VM) may be larger than the VM specified in for the Key Attribute, as defined for particular Matching Type. + The Specific Character Set (0008,0005) Attribute may be present in the Identifier but is never matched. Rather, it specifies how other Attributes are encoded in the Request and Response Identifiers. + It may influence how matching of other Attributes is performed. If Specific Character Set (0008,0005) is absent, then the default character repertoire shall be used. Specific Character Set (0008,0005) shall not have a zero length value. + Specific Character Set (0008,0005) may have multiple values if escape sequences are used to switch between character repertoires within values. + If the SCP does not support the value(s) of Specific Character Set (0008,0005) in the Request Identifier, then the manner in which matching is performed is undefined and shall be specified in the conformance statement. + + + + If an SCU sends a Request Identifier with a single byte character set not supported by the SCP, then it is likely, but not required, that the SCP will treat unrecognized characters as wild cards and match only on characters in the default repertoire, and return a response in the default repertoire. + + + Some Specific Character Set values are used with multi-component group person names (e.g., single-byte, ideographic and phonetic and phonetic component groups separated by an "=" (3DH) character), which may also affect the behavior of literal string matching. + + + + The Timezone Offset From UTC (0008,0201) Attribute may be present in the Identifier but is not matched if Timezone query adjustment is negotiated. If Timezone query adjustment is negotiated, it specifies how date and time Attribute values are interpreted in the Request and Response Identifiers if those values lack a specific time zone offset specification. +
+ Single Value Matching + If the value specified for a Key Attribute in a request is non-zero length and if it is: + + + not a date or time or datetime, contains no wild card characters + + + a date or time or datetime, contains a single date or time or datetime with no "-" + + + then single value matching shall be performed. Except for Attributes with a PN Value Representation, only entities with values that match exactly the value specified in the request shall match. This matching is case-sensitive, i.e., sensitive to the exact encoding of the key Attribute value in character sets where a letter may have multiple encodings (e.g., based on its case, its position in a word, or whether it is accented). + For Attributes with a PN Value Representation (e.g., Patient Name (0010,0010)), an application may perform literal matching that is either case-sensitive, or that is insensitive to some or all aspects of case, position, accent, or other character encoding variants. + + + + For multi-component names, the component group delimiter "=" (3DH) may be present in the Key Attribute value, but may give unexpected results if the SCP does not support matching on separate components but interprets the entire value literally as a single string. E.g., "Wang^XiaoDong=王^小東" may or may not match "Wang^XiaoDong" or "王^小東"; wild card matching without the component group delimiter, such as "*Wang^XiaoDong*" or "*王^小東 *" may be necessary. + + + Using attributes with VR of AE, LO, PN and SH as matching keys will not allow single value matching on values that contain characters "*" and "?" - such queries will always be treated as queries with wildcard matching. + + + Attributes with VR of ST, LT and UT are intended for conveying narrative text and may contain wildcard characters "*" and "?". Attempts to match on a string explicitly containing "*" or "?" will be treated as wildcard matching and thus may return multiple results rather than a single one. + + + + If extended negotiation of fuzzy semantic matching rather than literal matching of PN Value Representation is successful, not only may matching be insensitive to case, position, accent, and character encoding, but in addition other techniques such as phonetic matching may be applied. + If the Timezone Offset From UTC (0008,0201) Attribute is present in the Identifier and Timezone query adjustment was negotiated, it shall be used to adjust values of time Attributes (and associated date Attributes, if present) from the local timezone to UTC. It shall also adjust values of datetime Attributes that do not specify a timezone offset. The encoding and semantics of the Timezone Offset From UTC (0008,0201) Attribute shall be as defined in the SOP Common Module in . + The manner in which matching is performed is implementation dependent and shall be specified in the conformance statement. + + + + This definition implies that dates or times or datetimes are matched by their meaning, not as literal strings. For example: + + + the DT "19980128103000.0000" matches "19980128103000" + + + the DT "19980128103000" with no timezone offset matches "19980128073000" with timezone offset "-0300" + + + the TM "2230" matches "223000" + + + + + If an application is concerned about how single value matching of dates and times is performed by another application, it may consider using range matching instead, which is always performed by meaning, with both values in the range the same. + + + Exclusion of the "-" character for single value matching implies that a Key Attribute with DT Value Representation may not contain a negative offset from Universal Coordinated Time (UTC) if single value matching is intended. Use of the "-" character in date, time or datetime indicates range matching. + + + If an application is in a local time zone that has a negative offset then it cannot perform single value matching using a local time notation. Instead, it can convert the Key Attribute value to UTC and use an explicit suffix of "+0000". + + + Matching of PN Attributes may be accent-insensitive, as specified in the conformance statement. Accent-insensitive matching would successfully match, for instance, a query character "SMALL LETTER a" (06/01 in the default ISO-IR 6) with + + + "SMALL LETTER a WITH GRAVE ACCENT" (14/00 in ISO-IR 100), + + + "SMALL LETTER a WITH TILDE" (14/03 in ISO-IR 100), + + + "SMALL LETTER a WITH BREVE" (14/03 in ISO-IR 101), and + + + "CAPITAL LETTER a WITH ACUTE ACCENT" (12/01 in ISO-IR 100) (if matching is also case-insensitive), + + + but would not match 14/00 in ISO-IR 101, which is "SMALL LETTER r WITH ACUTE ACCENT". Matching to particular bit-combinations is specific to each supported character set (note the difference in meaning of 14/00), and should be described in the conformance statement. + + + An SCU application may elect to perform additional filtering of the responses by applying the matching rules itself. In the event that both the SCU and SCP are applying the matching rules, this process will be successful as long as literal matching is performed by both, and any additional SCU filtering is insensitive to case, position, accent, or other character encoding variants. + + + + However if fuzzy semantic matching of PN Attributes has been negotiated, matching by the SCP may result in responses that are not obviously related to the request, hence care should be taken if any additional filtering of responses is performed by the SCU. For example, if phonetic matching is performed, a query for "Swain" might well return "Swayne", or if name component order insensitive matching is performed, a query for "Smith^Mary" might well return "Mary^Smith" or "Mary Smith" or "Smith, Mary". Fuzzy semantic matching may also take into account separate single-byte, ideographic and phonetic name component groups. +
+
+ List of UID Matching + A List of UIDs is encoded by using the value multiplicity operator, backslash ("\"), as a delimiter between UIDs. Each item in the list shall contain a single UID value. Each UID in the list contained in the Identifier of the request may generate a match. + + A list of single values is encoded exactly as a VR of UI and a VM of Multiple (see ). + +
+
+ Universal Matching + If the value specified for a Key Attribute in a request is zero length, then all entities shall match this Attribute. An Attribute that contains a Universal Match specification in a C-FIND request provides a mechanism to request the selected Attribute value be returned in corresponding C-FIND responses. +
+
+ Wild Card Matching + If the Attribute is not a date, time, signed long, signed short, unsigned short, unsigned long, floating point single, floating point double, other byte string, other word string, unknown, Attribute tag, decimal string, integer string, age string or UID and the value specified in the request contains any occurrence of an "*" or a "?", then "*" shall match any sequence of characters (including a zero length value) and "?" shall match any single character. This matching is case sensitive, except for Attributes with an PN Value Representation (e.g., Patient Name (0010,0010)). + For Attributes with a PN value representation, including the case of extended negotiation of fuzzy semantic matching, wild card matching is implementation dependent and shall be specified in the conformance statement. + + + + Wild card matching on a value of "*" is equivalent to universal matching. + + + The wild card matching method specified by DICOM might not be supported by some non-DICOM multi-byte character text processors. + + + For multi-component group names, the component group delimiter "=" (3DH) may be present in the Key Attribute value, but may give unexpected results if the SCP does not support matching on separate components but interprets the entire value literally. E.g., "*=*" or "*=*=*" may or may not return all strings, and hence is not equivalent to "*", nor to universal matching. + + + Using attributes with VR of AE, LO, PN and SH as matching keys will not allow single value matching on values that contain characters "*" and "?" - such queries will always be treated as queries with wildcard matching. + + + Attributes with VR of ST, LT and UT are intended for conveying narrative text and may contain wildcard characters "*" and "?". Attempts to match on a string explicitly containing "*" or "?" will be treated as wildcard matching and thus may return multiple results rather than a single one. + + + +
+
+ Range Matching + In the absence of extended negotiation, if the Attribute is a date, then: + + + A string of the form "<date1> - <date2>", where <date1> is less or equal to <date2>, shall match all occurrences of dates that fall between <date1> and <date2> inclusive + + + A string of the form "- <date1>" shall match all occurrences of dates prior to and including <date1> + + + A string of the form "<date1> -" shall match all occurrences of <date1> and subsequent dates + + + In the absence of extended negotiation, if the Attribute is a time, then: + + + A string of the form "<time1> - <time2>", where <time1> is less or equal to <time2>, shall match all occurrences of times that fall between <time1> and <time2> inclusive + + + A string of the form "- <time1>" shall match all occurrences of times prior to and including <time1> + + + A string of the form "<time1> -" shall match all occurrences of <time1> and subsequent times + + + If the Attribute is a datetime, then: + + + A string of the form "<datetime1> - <datetime2>", where <datetime1> is less or equal to <datetime2>, shall match all moments in time that fall between <datetime1> and <datetime2> inclusive + + + A string of the form "- <datetime1>" shall match all moments in time prior to and including <datetime1> + + + A string of the form "<datetime1> -" shall match all moments in time subsequent to and including <datetime1> + + + The offset from Universal Coordinated Time, if present in the Value of the Attribute, shall be taken into account for the purposes of the match. + + + If extended negotiation of combined datetime matching is successful, then a pair of Attributes that are a date and a time, both of which specify the same form of range matching, shall have the concatenated string values of each range matching component matched as if they were a single datetime Attribute. + + For example, a Study Date of "20060705-20060707" and a Study Time of "1000-1800" will match the time period of July 5, 10am until July 7, 6pm, rather than the three time periods of 10am until 6pm on each of July 5, July 6 and July 7, as would be the case without extended negotiation. + + Regardless of other extended negotiation, an application may use the value of Timezone Offset From UTC (0008,0201) to adjust values of time and datetime Attributes from the local timezone to UTC for matching. See . + + If extended negotiation of combined datetime matching is successful, the timezone offset may effect a change in date if the local time and UTC are on different sides of midnight. + + Range matching is not defined for types of Attributes other than dates and times. +
+
+ Sequence Matching + If a Key Attribute in the Identifier of a C-FIND request needs to be matched against an Attribute structured as a Sequence of Items (Value Representation of Type SQ), the Key Attribute shall be structured as a Sequence of Items with a single Item. This Item may contain zero or more Item Key Attributes. Each Item Key Attribute matching shall be performed on an Item by Item basis. The types of matching defined in shall be used: Single Value Matching, List of UID Matching, Universal Matching, Wild Card Matching, Range Matching and Sequence Matching (recursive Sequence matching). + If all the Item Key Attributes match, for at least one of the Items of the Attribute against which the match is performed, a successful match is generated. A sequence of matching Items containing only the requested Attributes is returned in the corresponding C-FIND responses. + If the Key Attribute in the Identifier of a C-FIND request contains no Key Item Attribute (zero-length Item Tag), then all entities shall match this Attribute. This provides a universal matching like mechanism to request that the selected Key Attribute value (the entire Sequence of Items) be returned in corresponding C-FIND responses. +
+
+
+ Matching Multiple Values + When matching an Attribute that has a value multiplicity of greater than one, if any of the values match, then all values shall be returned. +
+
+
+
+ Standard Query/Retrieve Information Models + Three standard Query/Retrieve Information Models are defined in this Annex. Each Query/Retrieve Information Model is associated with a number of SOP Classes. The following three hierarchical Query/Retrieve Information Models are defined: + + + Patient Root + + + Study Root + + + Patient/Study Only + + +
+ Patient Root Query/Retrieve Information Model + The Patient Root Query/Retrieve Information Model is based upon a four level hierarchy: + + + Patient + + + Study + + + Series + + + Composite Object Instance + + + The patient level is the top level and contains Attributes associated with the Patient Information Entity (IE) of the Composite IODs as defined in . Patients IEs are modality independent. + The study level is below the patient level and contains Attributes associated with the Study IE of the Composite IODs as defined in . A study belongs to a single patient. A single patient may have multiple studies. Study IEs are modality independent. + The series level is below the study level and contains Attributes associated with the Series, Frame of Reference and Equipment IEs of the Composite IODs as defined in . A series belongs to a single study. A single study may have multiple series. Series IEs are modality dependent. To accommodate this modality dependence, the set of Optional Keys at the series level includes all Attributes defined at the series level from any Composite IOD defined in . + The lowest level is the Composite Object Instance level and contains Attributes associated with the Composite object IE of the Composite IODs as defined in . A Composite Object Instance belongs to a single series. A single series may contain multiple Composite Object Instances. Most composite object IEs are modality dependent. To accommodate this potential modality dependence, the set of Optional Keys at the Composite Object Instance level includes all Attributes defined at the Composite Object Instance level from any Composite IOD defined in . +
+
+ Study Root Query/Retrieve Information Model + The Study Root Query/Retrieve Information Model is identical to the Patient Root Query/Retrieve Information Model except the top level is the study level. Attributes of patients are considered to be Attributes of studies. +
+
+ Patient/Study Only Query/Retrieve Information Model + Retired. See PS 3.4-2004. +
+
+ Additional Query/Retrieve Attributes + Some optional Attributes that may be used in Query/Retrieve Information Models that are not Attributes of an Information Object Definition and, therefore, are not defined in . These Attributes are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Additional Query/Retrieve Attributes
+ + Attribute Name + + + + Tag + + + + Attribute Description + +
+ Number of Patient Related Studies + + (0020,1200) + + The number of studies that match the Patient level Query/Retrieve search criteria +
+ Number of Patient Related Series + + (0020,1202) + + The number of series that match the Patient level Query/Retrieve search criteria +
+ Number of Patient Related Instances + + (0020,1204) + + The number of Composite Object Instances that match the Patient level Query/Retrieve search criteria +
+ Number of Study Related Series + + (0020,1206) + + The number of series that match the Study level Query/Retrieve search criteria +
+ Number of Series Related Instances + + (0020,1209) + + The number of Composite Object Instances in a Series that match the Series level Query/Retrieve search criteria +
+ Number of Study Related Instances + + (0020,1208) + + The number of Composite Object Instances that match the Study level Query/Retrieve search criteria +
+ Modalities in Study + + (0008,0061) + + All of the distinct values used for Modality (0008,0060) in the Series of the Study. +
+ SOP Classes in Study + + (0008,0062) + + The SOP Classes contained in the Study. +
+ Alternate Representation Sequence + + (0008,3001) + + A Sequence of Items, each identifying an alternate encoding of an image that matches the Instance level Query/Retrieve search criteria (see ) +
+ If the SCP manages images in multiple alternate encodings, only one of the alternate encodings of an image is included in the number of object instances. +
+
+ New Instance Creation for Enhanced Multi-Frame Image Conversion + When Query/Retrieve View (0008,0053) is present with a value of "CLASSIC" or "ENHANCED" in a C-FIND, C-MOVE or C-GET Request Identifier, then the Information Model against which the query or retrieval is performed and any SOP Instances that are retrieved shall be returned, constructed or converted according to the requirements in this section. + There are no requirements with respect to when such instances are actually created or persisted, only that they be available on request. I.e., they may be created in advance (cached) or they may be created dynamically as required, as long as the process is deterministic in the sense that the same Attributes will be populated with the same values on successive queries and retrievals (including UIDs). + + + + The UID generation process is required to be deterministic but it is important to remember that appending a suffix to an existing UID is not a valid approach to generating a new UID, unless the converter is the producer (owner of the root) of the original UID and knows that this is safe and the result will be unique. + + + The cross-references between original and converted instances contain sufficient information to recover UIDs in the alternative form. + + + + All instances for a Patient known to the SCP shall be converted as necessary to maintain referential integrity and to avoid information loss. + + + + It is not permitted to fail to include a subset of instances within this scope, for example, the presentation states or key object selection documents, in the "ENHANCED" view, in order to avoid the effort of creating new instances with updated references required to maintain referential integrity. In other words, the total "information content" of any view will be no less than that of the default view. + + + This does not mean that all instances need to be converted, since if they contain no such references, they can be left alone and included in the view. For example, a Classic single slice CT localizer image with no references can remain unchanged in the view as a CT Image Storage SOP Class with its existing SOP Instance UID and SOP Class and in its existing Series, and be referenced from converted instances, such as the axial images prescribed from it. An SCU cannot make any assumptions about what will or will not be converted, or in what order. + + + It is understood that the requirements of this section are applicable to a single SCP; it is not possible to require all SCPs that perform conversion to perform it the same way, or create the same UIDs, etc. + + + + In addition to the general requirements in this section, specific requirements apply to the following types of instance created: + + + Enhanced (true or legacy converted) multi-frame images that are created from Classic single frame images + + + Classic single frame images that are created from Enhanced (true or legacy converted) multi-frame images + + + Instances that contain references to the SOP Instance UIDs or Series Instance UIDs corresponding to either the converted single frame images, or other instances with such references + + + The general requirements are that: + + + The new Composite Instance shall have a new SOP Instance UID. + + + The new Composite Instance shall be a valid SOP Instance (i.e., will comply with the IOD, Module and Attribute requirements for the Storage SOP Class). + + + - The new Composite Instance shall contain the Contributing Equipment Sequence (0018,A001). If the source Composite Instances already contain the Contributing Equipment Sequence with a consistent set of Item values (excluding Contribution DateTime (0018,A002)), then a new Item shall be appended to the copy of the sequence in the new Composite Instance; if the source Composite Instance does not contain the Contributing Equipment Sequence or the Item values (excluding Contribution DateTime (0018,A002)) differ between source instances, then Contributing Equipment Sequence shall be created, containing one new Item. In either case, the new Item shall describe the equipment that is creating the new Composite Instance, and the Purpose of Reference Code Sequence (0040,A170) within the Item shall be (109106, DCM, "Enhanced Multi-frame Conversion Equipment") and the + Contribution Description (0018,A003) shall be "Legacy Enhanced Image created from Classic Images", "Classic Image created from Enhanced Image", or "Updated UID references during Legacy Enhanced Classic conversion" as appropriate. + + + The new Composite Instance shall have the same Patient and Study level information as the source Instance, including the same Study Instance UID. + + + The new Composite Instance shall have the same spatial and temporal Frame of Reference information as the source instance, if present (e.g., the Frame of Reference UID shall be the same). + + + The new Composite Instance shall be placed in a new Series (together with other new Composite Instances that share the same, new Series level information), with a new Series Instance UID. The Series Date (0008,0021) and Series Time (0008,0031) of all the Instances in the new Series shall be the earliest of the values in the source Composite Instances, if present. + + + + The new Series Date and Time shall NOT be that of when the conversion was performed, but shall reflect the values in the source images. + + + There is no standard requirement or mechanism defined to change or preserve other Series level Attributes, such as Series Number or Series Description. This is left to the discretion of the implementer, particularly in cases where instances from different Series are merged. + + + + + + The new Composite Instance shall have the same Items and Values of Request Attributes Sequence (0040,0275) as the source Composite Instances, if Request Attributes Sequence (0040,0275) is present in any of the source Composite Instances. + + + If the new Composite Instance contains references to another entity for the same Patient (including, but not limited to, references to SOP Instances, Series, Studies or Frames of Reference), and the target of those references is also converted, then the references shall be changed to refer to the converted entity. + + + + For example, if the source instance refers to an instance in a Series, and the referenced instance is also converted, and hence placed in a new Series, then both the SOP Instance UID and the Series Reference UID in the hierarchical reference to the instance will need to be updated, as will the SOP Class UID of the referenced instance, if that has changed, as it likely will have. + + + The overall intent is to maintain referential integrity within the converted set of instances, within the scope of the same Patient. Since it is likely that most if not all non-image instances for a patient will reference images that will be converted, this means that most if not all non-image instances will also have to be "converted", for the purpose of updating such references. This referential integrity is required regardless of whether the initial request is for a subset of instances for the patient only, or not. + + + The UIDs referenced in Conversion Source Attributes Sequence (0020,9172) are not converted, since by definition, these reference instances in the "other" view; they should not exist in the source, but will be inserted (or be replaced, if previously converted) during conversion. + + + + + + The specific requirements for the conversion of single frame images to Enhanced Multi-frame images are: + + + The SOP Class of the new Composite Instance shall be the appropriate modality-specific Enhanced Image Storage SOP Class that is intended for de novo creation by an acquisition or post-processing device, unless the source images do not contain sufficient information to populate mandatory Attributes with standard Enumerated Values and Defined Terms or Coded Sequence Item values, in which case the appropriate modality-specific Legacy Converted Enhanced Image Storage SOP Class shall be used. The appropriate SOP Classes are defined in . + + + + For example, if the source images to be converted are of the CT Image Storage SOP Class, then the preferred new SOP Class is the Enhanced CT Image Storage SOP Class, but if this is not possible, the Legacy Converted Enhanced CT Image Storage SOP Class is used. + + + It is not intended that images from different modalities be combined in the same new Composite Instance. For example, it is not expected that CT and PET images would be combined in the same Instance, since the technique Attributes and the pixel data characteristics are quite distinct. + + + It is expected that as many single frame images will be combined into a single multi-frame image as is sensible, given the constrains on what Attributes must be identical as defined in this section, and depending on the type of images and the size of the resulting object. Different implementations may make different choices in this respect. For example, an application might choose to combine only images in the same Series, or with the same slice spacing, or the same values for Image Type, or with the same Image Orientation (Patient). + + + + + + The new Composite Instance shall not be contained in a Concatenation. This means that it shall not contain a Concatenation UID (0020,9161) Attribute or other Concatenation Attributes. If the existing Composite Instance contains such Attributes, they shall not be included in the new Composite Instance. + + + The new Composite Instance contains only one set of Attributes for the Image Pixel Module, hence the contents of the Image Pixel Module shall either be identical in all source images, or the Pixel Data for each frame shall be converted as necessary to match the Image Pixel Module of the new Composite Instance. + + + + In particular this means that the values of Rows, Columns, Bits Stored, Bits Allocated, High Bit, Pixel Representation, Samples per Pixel, Photometric Interpretation and Planar Configuration applicable to all of the frames needs to be the same. In special cases, such as where Bits Stored is less than Bits Allocated but varies per frame, it may be safe to use the largest value for all the frames and ensure that any unused high bits are appropriately masked before encoding. It is not expected that source images with different numbers of Rows and Columns will be combined (by padding the periphery of images smaller than the largest); quite apart from not being the intended use case, this has the potential to greatly expand the size of the instance, and might also require adjustment of the Image Position (Patient) values. + + + Special attention should be given to the Pixel Padding Value and associated Attributes, in case these vary per frame in the source images, in which case the Pixel Data for some frames may need to be modified to be consistent with all the other frames. + + + It is possible to change the Image Pixel Module Attributes related to compressed Transfer Syntaxes (including lossy or irreversible compression) during conversion. + + + + + + All mandatory Attributes of all mandatory Modules and Functional Group Macros of the SOP Class of the new Composite Instance shall be populated as required by the IOD. In this context, "mandatory" means either required or conditional where the condition is satisfied. + + For example, if the source images to be converted are of the CT Image Storage SOP Class, and the new Composite Instance is of the Legacy Converted Enhanced CT Image Storage SOP Class, then it is required that the Pixel Measures Functional Group be populated from Pixel Spacing, that the Plane Position (Patient) Functional Group be populated from Image Position (Patient), etc. In addition, if Body Part Examined is present in the source images with a standard value, then the condition for the inclusion of the Frame Anatomy Functional Group is satisfied, and the value therein needs to be converted to the appropriate Anatomic Region Sequence code. + + + + All optional Attributes, Modules and Functional Group Macros for which corresponding information is present in the source images in standard Attributes shall also be populated. + + + All Attributes of the Overlay Module shall be removed and converted into a Grayscale or Color Softcopy Presentation State (depending on the value of Photometric Interpretation); if the Overlay uses high bits in the Pixel Data (7FE0,0010) these shall be extracted and encoded in Overlay Data (60xx,3000) in the Presentation State and shall be set to zero in the Pixel Data (7FE0,0010) Attribute in the converted image. + + The extraction of Overlays from multiple frames may lead to a proliferation of GSPS Instances (one per converted frame), unless the converter recognizes commonality in the binary values of overlay bit planes and factors it out into fewer GSPS objects that each apply to multiple frames. + + + + All Attributes of the Curve Module (retired, but formerly defined in DICOM) shall be removed; they may be converted into a Grayscale or Color Softcopy Presentation State (depending on the value of Photometric Interpretation) or a Waveform as appropriate, but this is not required. + + + All Attributes of the Graphic Annotation Sequence (0070,0001) (not defined in Classic image IODs, but sometimes used in a Standard Extended SOP Class) shall be removed; they may be converted into a Grayscale or Color Softcopy Presentation State (depending on the value of Photometric Interpretation), but this is not required. + + + All remaining Attributes in the source images (i.e., those that have not been used to populate mandatory or optional Attributes in Modules and Functional Groups), including Private Attributes, shall be copied into the top-level Data Set or the Unassigned Shared Converted Attributes Sequence (0020,9170) if they are present in all of the source images for the new Composite Instance, have the same number of values, and have the same values, otherwise they shall be copied into the Unassigned Per-Frame Converted Attributes Sequence (0020,9171). + + The semantics of Private Attributes, or Standard Attributes used in a Standard Extended SOP Class, might not be maintained, being unknown to the converting application; for example, referential integrity of UIDs in Private Attributes might not be updated. + + + + The new Composite Instance shall contain references to the source Instances from which it was converted, encoded in the Conversion Source Reference Functional Group Macro. + + + The specific requirements for the conversion of Enhanced Multi-frame images to Classic single frame images are: + + + The SOP Class of the new Composite Instance shall be the appropriate modality-specific (Classic) Image Storage SOP Class that is intended for de novo creation by an acquisition or post-processing device. + + For example, if the source images to be converted are of the Enhanced CT Image Storage SOP Class or the Legacy Converted Enhanced CT Image Storage SOP Class, then the new SOP Class is the CT Image Storage SOP Class. + + + + All mandatory Attributes of the IOD of the SOP Class of the new Composite Instance shall be populated. In this context, "mandatory" means either required or conditional where the condition is satisfied. + + For example, if the source images to be converted are of the Legacy Converted Enhanced CT Image Storage SOP Class, and the new Composite Instance is of the CT Image Storage SOP Class, then it is required that Pixel Spacing be populated from the Pixel Measures Functional Group, that Image Position (Patient) be populated from the Plane Position (Patient) Functional Group, etc. + + + + All optional Attributes in Modules of the IOD for which corresponding information is present in the source images shall also be populated. + + + All remaining Attributes in the source images (i.e., those that have not been used to populate mandatory or optional Attributes in Modules), including Private Attributes, shall be copied from the top-level Data Set and the Shared Functional Group Macro and the corresponding Item of the Per-Frame Functional Group Macro into the top-level Data Set of the new Composite Instance, including those in the Unassigned Shared Converted Attributes Sequence (0020,9170) and the corresponding Item of the Unassigned Per-Frame Converted Attributes Sequence (0020,9171) (which will result in a Standard Extended SOP Class). + + + + Identifying Attributes, such as Series Number or Series Description, will be present in the Unassigned functional groups, and UIDs will be present in the Conversion Source Attributes Sequence, allowing, for example, the original Series organization to be recovered, whether or not a single Series was previously converted into a single Legacy Converted instance or it was split or merged with other Series. + + + The integrity of the set of Private Attributes recovered in this manner cannot be guaranteed to result in the correct function of any applications that depend on them, but the expectation is that this will be no better or worse than the impact of storing instances with private Attributes on any Storage SCP that may or may not reorganize and/or selectively preserve Private Attributes. + + + + + + The new Composite Instance shall contain references to the source Instances from which it was converted, encoded in the Conversion Source Attributes Sequence (0020,9172) in the SOP Common Module. + + + The specific requirements for the conversion of other instances are: + + + The new Composite Instance shall be an instance of the same SOP Class as the source Composite Instance. + + + The new Composite Instance shall contain references to the source Instances from which it was converted, encoded in the Conversion Source Attributes Sequence (0020,9172) in the SOP Common Module. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modality-Specific SOP Class Conversions
+ + Classic + + + + True Enhanced + + + + Legacy Converted Enhanced + +
+ CT Image Storage + + Enhanced CT Image Storage + + Legacy Converted Enhanced CT Image Storage +
+ MR Image Storage + + Enhanced MR Image Storage + + Legacy Converted Enhanced MR Image Storage +
+ PET Image Storage + + Enhanced PET Image Storage + + Legacy Converted Enhanced PET Image Storage +
+
+
+
+ DIMSE-C Service Groups + Three DIMSE-C Services are used in the construction of SOP Classes of the Query/Retrieve Service Class. The following DIMSE-C operations are used: + + + C-FIND + + + C-MOVE + + + C-GET + + +
+ C-FIND Operation + SCPs of some SOP Classes of the Query/Retrieve Service Class may be capable of processing queries using the C-FIND operation as described in . The C-FIND operation is the mechanism by which queries are performed. Matches against the keys present in the Identifier are returned in C-FIND responses. +
+ C-FIND Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Query/Retrieve Information Model against which the C-FIND is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-FIND operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-FIND operation with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. +
+
+ Identifier + Both the C-FIND request and response contain an Identifier encoded as a Data Set (see ). + + The definition of a Data Set in specifically excludes the range of groups below group 0008, and this includes in particular Meta Information Header elements such as Transfer Syntax UID (0002,0010). The C-FIND request and identifier do not support a mechanism for ascertaining the manner in which an SCP might have encoded a stored image whether it be by requesting Transfer Syntax UID (0002,0010) or by any other mechanism. + +
+ Request Identifier Structure + An Identifier in a C-FIND request shall contain: + + + Key Attributes values to be matched against the values of storage SOP Instances managed by the SCP. + + + Query/Retrieve Level (0008,0052), which defines the level of the query. + + + Conditionally, the Attribute Query/Retrieve View (0008,0053). This Attribute may be included if Enhanced Multi-Frame Image Conversion has been accepted during Association Extended Negotiation. It shall not be included otherwise. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Request Identifier. It shall not be included otherwise. + + + Conditionally, the Attribute Timezone Offset From UTC (0008,0201). This Attribute shall be included if Key Attributes of time are to be interpreted explicitly in the designated local time zone. It shall not be present otherwise, i.e., it shall not be sent with a zero-length value. + + + The Key Attributes and values allowable for the level of the query shall be defined in the SOP Class definition for the Query/Retrieve Information Model. +
+
+ Response Identifier Structure + The C-FIND response shall not contain Attributes that were not in the request or specified in this section. + An Identifier in a C-FIND response shall contain: + + + Key Attributes with values corresponding to Key Attributes contained in the Identifier of the request. + + + + All Required Keys in the Request Identifier, as well as all Optional Keys in the Request Identifier that are supported by the SCP, will therefore be present in the Response Identifier. + + + Required Keys and supported Optional Keys in the Response Identifier will have zero length if the SCP has no value to send; i.e., there is no requirement that the SCP have a value for these, or create a dummy value. + + + The requirement that unsupported Optional Keys present in the Request Identifier not be included in the Response Identifier is specified in . + + + + + + Query/Retrieve Level (0008,0052), which defines the level of the query. The Query/Retrieve level shall be equal to the level specified in the request. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Response Identifier. It shall not be included otherwise. The C-FIND SCP is not required to return responses in the Specific Character Set requested by the SCU if that character set is not supported by the SCP. The SCP may return responses with a different Specific Character Set. + + + Conditionally, the Attribute Timezone Offset From UTC (0008,0201). This Attribute shall be included if any Attributes of time in the Response Identifier are to be interpreted explicitly in the designated local time zone. It shall not be present otherwise, i.e., it shall not be sent with a zero-length value. + + + The C-FIND SCP is required to support either or both the Retrieve AE Title Data Element or the Storage Media File-Set ID/Storage Media File Set UID Data Elements. An Identifier in a C-FIND response shall contain: + + + Storage Media File-Set ID (0088,0130), which defines a user or implementation specific human readable Identifier that identifies the Storage Media on which the Composite Object Instance(s); reside. This element pertains to the set of Composite Object Instances available at the Query/Retrieve Level specified in the Identifier of the C-FIND request (e.g., Patient, Study, Series, Composite Object Instance). This Attribute shall be present if the Retrieve AE Title Data Element is not present. A null value (Data Element length of 0) is valid for all levels except the lowest level in the Information Model as defined by the SOP Class. + + + Storage Media File-Set UID (0088,0140), which uniquely identifies the Storage Media on which the Composite Object Instance(s) reside. This element pertains to the set of Composite Object Instances available at the Query/Retrieve Level specified in the Identifier of the C-FIND request (e.g., Patient, Study, Series, Composite Object Instance). This Attribute shall be present if the Retrieve AE Title Data Element is not present. A null value (Data Element length of 0) is valid for all levels except the lowest level in the Information Model as defined by the SOP Class. + + The File-Set concepts are used in . + + + + Retrieve AE Title (0008,0054), which defines a list of DICOM Application Entity Title(s) that identify the location from which the Composite Object Instance(s) may be retrieved on the network. This element pertains to the set of Composite Object Instances available at the Query/Retrieve Level specified in the Identifier of the C-FIND request (e.g., Patient, Study, Series, Composite Object Instance). This Attribute shall be present if the Storage Media File-Set ID and Storage Media File-Set UID elements are not present. The Application Entity named in this field shall support either the C-GET or C-MOVE SOP Class of the Query/Retrieve Service Class. A null value (Data Element length of 0) is valid for all levels except the lowest level in the Information Model as defined by the SOP Class. + + + + For example, a DICOM AE with the AE Title of "A" performs a C-FIND request to a DICOM AE with the AE Title of "B" with the Query/Retrieve level set to "STUDY". DICOM AE "B" determines that the Composite Object Instances for each matching study may be retrieved by itself and sets the Data Element Retrieve AE Title to "B". + + + File-Sets may not be defined at every Query/Retrieve Level. If the SCP supports the File-Set ID/File-Set UID option but does not define these Attributes at the Query/Retrieve Level specified in the C-FIND request it may return these Data Elements with a length of 0 to signify that the value is unknown. An SCU should reissue a C-FIND at a Query/Retrieve Level lower in the hierarchy. + + + The fact that the value of the Key Attribute is unknown to the SCP of the Query/Retrieve Service Class does not imply that it is not present in the underlying Information Object. Thus, a subsequent retrieval may cause a Storage of a SOP Instance that contains the value of the Attribute. + + + + + + The C-FIND SCP may also, but is not required to, support the Instance Availability (0008,0056) Data Element. This Data Element shall not be included in a C-FIND request. An Identifier in a C-FIND response may contain: + + + Instance Availability (0008,0056), which defines how rapidly Composite Object Instance(s); become available for transmission after a C-MOVE or C-GET retrieval request. This element pertains to the set of Composite Object Instances available at the Query/Retrieve Level specified in the Identifier of the C-FIND request (e.g., Patient, Study, Series, Composite Object Instance). When some composite instances are less rapidly available than others, the availability of the least rapidly available shall be returned. If this Data Element is not returned, the availability is unknown or unspecified. A null value (Data Element length of 0) is not permitted. The Enumerated Values for this Data Element are: + + + "ONLINE", which means the instances are immediately available, + + + "NEARLINE", which means the instances need to be retrieved from relatively slow media such as optical disk or tape, or require conversion that takes time, + + + "OFFLINE", which means the instances need to be retrieved by manual intervention, + + + "UNAVAILABLE", which means the instances cannot be retrieved. Note that SOP Instances that are unavailable may have an alternate representation that is available (see section ). + + + + +
+
+
+ Status + + defines the specific status code values that might be returned in a C-FIND response. General status code values and fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-FIND Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources + + A700 + + (0000,0902) +
+ + Identifier does not match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ + Unable to process + + Cxxx + + (0000,0901) + (0000,0902) +
+ Cancel + + Matching terminated due to Cancel request + + FE00 + + None +
+ Success + + Matching is complete - No final Identifier is supplied. + + 0000 + + None +
+ Pending + + Matches are continuing - Current Match is supplied and any Optional Keys were supported in the same manner as Required Keys. + + FF00 + + Identifier +
+ + Matches are continuing - Warning that one or more Optional Keys were not supported for existence and/or matching for this Identifier. + + FF01 + + Identifier +
+
+
+
+ C-FIND SCU Behavior + This Section discusses both the baseline and extended behavior of the C-FIND SCU. +
+ Baseline Behavior of SCU + All C-FIND SCUs shall be capable of generating query requests that meet the requirements of the Hierarchical Search. + The Identifier contained in a C-FIND request shall contain a single value in the Unique Key Attribute for each level above the Query/Retrieve level. No Required or Optional Keys shall be specified that are associated with levels above the Query/Retrieve level. + The Unique Key Attribute associated with the Query/Retrieve level shall be contained in the C-FIND request and may specify Single Value Matching, Universal Value Matching, or List of UID Matching. In addition, Required and Optional Keys associated with the Query/Retrieve level may be contained in the Identifier. + An SCU conveys the following semantics using the C-FIND request: + + + The SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information it possesses down to the Query/Retrieve level specified in the request. + + + + The SCU may not assume the SCP supports any Optional Keys. Hence, Optional Keys serve only to reduce network related overhead when they are supported by the SCP. + + + The SCU must be prepared to filter C-FIND responses when the SCP fails to support an Optional Key specified in the C-FIND request. + + + + + + The SCU shall interpret Pending responses to convey the Attributes of a match of an Entity at the level of the query. + + + The SCU shall interpret a response with a status equal to Success, Failed or Refused to convey the end of Pending responses. + + + The SCU shall interpret a Refused or Failed response to a C-FIND request as an indication that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-FIND-CANCEL request at any time during the processing of the C-FIND. The SCU shall recognize a status of Canceled to indicate that the C-FIND-CANCEL was successful. + + +
+
+ Extended Behavior of SCU + Extended SCU behavior shall be negotiated at Association establishment time. If an option within the extended behavior is not agreed upon in the negotiation, then only baseline SCU behavior shall be performed with respect to that option. Extended SCU behavior includes all baseline behavior with the following option: + + + Relational-queries + + + Enhanced Multi-Frame Image Conversion + + + More than one option may be agreed upon. +
+ Relational-Queries + The C-FIND Service with relational-queries allows any combination of keys at any level in the hierarchy. The Unique Key Attribute associated with the Query/Retrieve level shall be contained in the C-FIND request and may specify Single Value Matching, Universal Value Matching, or List of UID Matching. Support for relational-queries removes the baseline restriction that a Unique Key shall be specified for all levels above the Query/Retrieve level in the C-FIND request. +
+
+ Enhanced Multi-Frame Image Conversion + The C-FIND Service with Enhanced Multi-Frame Image Conversion allows for selection of the default or an alternative view of the instances represented by the Information Model. + Support for Enhanced Multi-Frame Image Conversion allows the SCU to specify the Query/Retrieve View (0008,0053) in the Request Identifier with a value of either "CLASSIC" or "ENHANCED". + If Query/Retrieve View (0008,0053) is not present in the Request Identifier, then the SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information about the instances that it possesses, as received. + If Query/Retrieve View (0008,0053) is present with a value of "CLASSIC", then the SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information about Classic single frame Instances (converted from Enhanced multi-frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + If Query/Retrieve View (0008,0053) is present with a value of "ENHANCED", then the SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information about Enhanced multi-frame Instances (converted from Classic single frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + + + + The SCU may assume that no duplicate information will be returned. For example, if an entire series of single frame instances can be converted to a separate series of converted instances, a STUDY level C-FIND will not return both series. + + + The Query Information Model is unchanged, and the same unique, required and optional keys are equally applicable to both views, except that the values for the SERIES and IMAGE level queries will be different and will depend on the converted instance content. + + + Unconverted instances, such as for other modalities like Ultrasound, will appear identical regardless of view. + + + Implementations may apply performance optimizations, such as pre-computing or caching the potential information against which CLASSIC and ENHANCED queries may be performed, in order to minimize significant delays between the query request and response caused by converting "on demand", but SCUs may need to consider the potential for a delayed response when configuring timeouts, etc. + + + +
+
+
+
+ C-FIND SCP Behavior + This Section discusses both the baseline and extended behavior of the C-FIND SCP. +
+ Baseline Behavior of SCP + All C-FIND SCPs shall be capable of processing queries that meet the requirements of the Hierarchical Search. + An SCP conveys the following semantics with a C-FIND response: + + + The SCP is requested to perform a match of all the keys specified in the Identifier of the request, against the information it possesses, to the level specified in the request. Attribute matching is performed using the key values specified in the Identifier of the C-FIND request as defined in . + + + The SCP generates a C-FIND response for each match using the Hierarchical Search method. All such responses shall contain an Identifier whose Attributes contain values from a single match. All such responses shall contain a status of Pending. + + + When all matches have been sent, the SCP generates a C-FIND response that contains a status of Success. A status of Success shall indicate that a response has been sent for each match known to the SCP. + + When there are no matches, then no responses with a status of Pending are sent, only a single response with a status of Success. + + + + The SCP shall generate a response with a status of Refused or Failed if it is unable to process the request. A Refused or Failed response shall contain no Identifier. + + + If the SCP receives C-FIND-CANCEL indication before it has completed the processing of the matches it shall interrupt the matching process and return a status of Canceled. + + + If the SCP manages images in multiple alternate encodings (see ), only one of the alternate encodings of an image shall be included in the set of matches for a C-FIND request at the Instance level. + + For query of images with alternate encodings, the SCP may select the appropriately encoded Instance for the request response based on identity of the SCU or other factors. + + + +
+ Hierarchical Search Method + Starting at the top level in the Query/Retrieve Information Model, continuing until the level specified in the C-FIND request is reached, the following procedures are used to generate matches: + + + If the current level is the level specified in the C-FIND request, then the key match strings contained in the Identifier of the C-FIND request are matched against the values of the Key Attributes for each entity at the current level. For each entity for which the Attributes match all of the specified match strings, construct an Identifier. This Identifier shall contain all of the Unique Keys at higher levels and all of the values of the Attributes for this entity that match those in the C-FIND request. Return a response for each such Identifier. If there are no matching keys, then there are no matches, return a response with a status equal to Success and with no Identifier. + + + Otherwise, if the current level is not the level specified in the C-FIND request and there is an entity matching the Unique Key Attribute value for this level specified in the C-FIND request, perform this procedure at the next level down in the hierarchy. + + + Otherwise there are no matches; return a response with a status equal to Success. + + The above description specifies a recursive procedure. It may recur upon itself multiple times as it goes down the hierarchical levels, but at each level it recurs only once. + + + +
+
+
+ Extended Behavior of SCP + Extended SCP behavior shall be negotiated at Association establishment time. If an option within the extended behavior is not agreed upon in the negotiation, then only baseline SCP behavior shall be performed with respect to that option. Extended SCP behavior includes all baseline behavior with the following option: + + + Relational-queries + + + Enhanced Multi-Frame Image Conversion + + + More than one option may be agreed upon. +
+ Relational-Queries + The C-FIND Service with relational-queries allows any combination of keys at any level in the hierarchy. At the lowest level, a query using the relational-queries shall contain the Unique Key for that level with either a single value match, a wild card match, or a universal match. Support for relational-queries removes the baseline restriction that a Unique Key shall be specified for all levels above the Query/Retrieve level in the C-FIND request. + The C-FIND SCP shall perform matching based on all keys specified in the C-FIND request regardless of the Query/Retrieve level. +
+
+ Relational Search Method + A query using the relational method may contain any combination of keys at any level in the hierarchy. Starting at the top level in the Query/Retrieve Information Model, continuing until the Query/Retrieve level specified in the C-FIND request is reached, the following procedures are used to generate matches: + + + The key match strings contained in the Identifier of the C-FIND request are matched against the values of the Key Attributes for each entity at the current level. + + + If no Key Attribute is specified at the current level and the current level is not the level specified in the C-FIND request, the match shall be performed as if a wild card were specified for the Unique Key Attribute for the current level (i.e., all entities at the current level shall match). + + + If the current level is the level specified in the C-FIND request, then for each matching entity (a matching entity is one for which the Attributes match all of the specified match strings in the Key Attributes), construct an Identifier. This Identifier shall contain all of the Attributes generated by this procedure at higher levels on this recursion path and all of the values of the Key Attributes for this entity that match those in the C-FIND request. + + + Otherwise, if the current level is not the level specified in the C-FIND request, then for each matching entity construct a list of Attributes containing all of the matching Key Attributes and all Attributes that were prepared at the previous level for this entity. Then perform this procedure at the next level down in the hierarchy for each matching entity. + + + Otherwise, if there are no matches, return a response with status equal to Success and no Identifier. + + + + + + The above description specifies a recursive procedure. It may recur upon itself multiple times as it goes down the hierarchical levels, and at each level, it may recur multiple times (one for each matching entity). This may result in a large number of Identifiers being generated. + + + It is not required that the above defined procedure be used to generate matches. It is expected that implementations will incorporate different algorithms for performing searches of the databases. For a given query, the set of matches shall be equivalent to that which would be generated by the above procedure. + + + +
+
+ Enhanced Multi-Frame Image Conversion + If Query/Retrieve View (0008,0053) is not present in the Request Identifier, then the SCP shall perform a match of all keys specified in the Identifier of the request against the information about the instances that it possesses, as received. + If Query/Retrieve View (0008,0053) is present with a value of "CLASSIC", then the SCP shall perform a match of all keys specified in the Identifier of the request against the information about Classic single frame Instances (converted from Enhanced multi-frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + If Query/Retrieve View (0008,0053) is present with a value of "ENHANCED", then the SCP shall perform a match of all keys specified in the Identifier of the request against the information about Enhanced multi-frame Instances (converted from Classic single frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + + + + The SCP will not return information that is duplicated. For example, if an entire series of single frame instances can be converted to a separate series of converted instances, a STUDY level C-FIND will not return both series. + + + The Query Information Model is unchanged, and the same unique, required and optional keys are equally applicable to both views, except that the values for the SERIES and IMAGE level queries will be different and will depend on the converted instance content. + + + Unconverted instances, such as for other modalities like Ultrasound, will appear identical regardless of view. + + + +
+
+
+
+
+ C-MOVE Operation + SCUs of some SOP Classes of the Query/Retrieve Service Class may generate retrievals using the C-MOVE operation as described in . The C-MOVE operation allows an application entity to instruct another application entity to transfer stored SOP Instances to another application entity using the C-STORE operation. Support for the C-MOVE service shall be agreed upon at Association establishment time by both the SCU and SCP of the C-MOVE in order for a C-MOVE operation to occur over the Association. The C-STORE sub-operations shall always be accomplished over an Association different from the Association that accomplishes the C-MOVE operation. Hence, the SCP of the Query/Retrieve Service Class serves as the SCU of the Storage Service Class. + + The application entity that receives the stored SOP Instances may or may not be the originator of the C-MOVE operation. + + A C-MOVE request may be performed to any level of the Query/Retrieve Information Model. However, the transfer of stored SOP Instances may not be performed at this level. The level at which the transfer is performed depends upon the SOP Class (see ). +
+ C-MOVE Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Query/Retrieve Information Model against which the C-MOVE is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-MOVE operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-MOVE operation and corresponding C-STORE sub-operations with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing, and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. The same priority shall be used for all C-STORE sub-operations. +
+
+ Move Destination + Move Destination specifies the Application Entity Title of the receiver of the C-STORE sub-operations. +
+
+ Identifier + The C-MOVE request shall contain an Identifier. The C-MOVE response shall conditionally contain an Identifier as required in . + + The Identifier is specified as U in the definition of the C-MOVE primitive in but is specialized for use with this service. + +
+ Request Identifier Structure + An Identifier in a C-MOVE request shall contain: + + + Query/Retrieve Level (0008,0052), which defines the level of the retrieval + + + Unique Key Attributes, which may include Patient ID (0010,0020), Study Instance UIDs (0020,000D), Series Instance UIDs (0020,000E), and the SOP Instance UIDs (0008,0018) + + + Conditionally, the Attribute Query/Retrieve View (0008,0053). This Attribute may be included if Enhanced Multi-Frame Image Conversion has been accepted during Association Extended Negotiation. It shall not be included otherwise. + + + Specific Character Set (0008,0005) shall be present if Patient ID (0010,0020) is using a character set other than the default character repertoire. + The Unique Keys at each level of the hierarchy and the values allowable for the level of the retrieval shall be defined in the SOP Class definition for the Query/Retrieve Information Model. + + + + In the non-Relational behavior, more than one entity may be retrieved if the Query/Retrieve Level is IMAGE, SERIES or STUDY, using List of UID matching, but only Single Value Matching value may be specified for Patient ID (0010,0020). + + + The issuer of the Patient ID (0010,0020) is implicit; there is no provision to send the Issuer of Patient ID (0010,0021). When there is a possibility of ambiguity of the Patient ID (0010,0020) value, a STUDY level retrieval should be used instead of a PATIENT level retrieval. + + + +
+
+ Response Identifier Structure + The Failed SOP Instance UID List (0008,0058) specifies a list of UIDs of the C-STORE sub-operation SOP Instances for which this C-MOVE operation has failed. An Identifier in a C-MOVE response shall conditionally contain the Failed SOP Instance UID List (0008,0058) based on the C-MOVE response status value. If no C-STORE sub-operation failed, Failed SOP Instance UID List (0008,0058) is absent and therefore no Data Set shall be sent in the C-MOVE response. + Specific Character Set (0008,0005) shall not be present. + The Identifier in a C-MOVE response with a status of: + + + Canceled, Failure, Refused, or Warning shall contain the Failed SOP Instance UID List Attribute + + + Pending shall not contain the Failed SOP Instance UID List Attribute (no Data Set) + + +
+
+
+ Status + + defines the specific status code values that might be returned in a C-MOVE response. General status code values and fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-MOVE Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources - Unable to calculate number of matches + + A701 + + (0000,0902) +
+ + Refused: Out of Resources - Unable to perform sub-operations + + A702 + + (0000,1021) + (0000,1022) + (0000,1023) +
+ + Refused: Move Destination unknown + + A801 + + (0000,0902) +
+ + Identifier does not match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ + Unable to Process + + Cxxx + + (0000,0901) + (0000,0902) +
+ Cancel + + Sub-operations terminated due to Cancel Indication + + FE00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Warning + + Sub-operations Complete - One or more Failures + + B000 + + (0000,1021) + (0000,1022) + (0000,1023) +
+ Success + + Sub-operations Complete - No Failures + + 0000 + + (0000,1021) + (0000,1022) + (0000,1023) +
+ Pending + + Sub-operations are continuing + + FF00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+
+
+ Number of Remaining Sub-Operations + Inclusion of the Number of Remaining Sub-operations is conditional based upon the status in the C-MOVE response. The Number of Remaining Sub-operations specifies the number of Remaining C-STORE sub-operations necessary to complete the C-MOVE operation. + A C-MOVE response with a status of: + + + Pending shall contain the Number of Remaining Sub-operations Attribute + + + Canceled may contain the Number of Remaining Sub-operations Attribute + + + Warning, Failure, or Success shall not contain the Number of Remaining Sub-operations Attribute + + +
+
+ Number of Completed Sub-Operations + Inclusion of the Number of Completed Sub-operations is conditional based upon the status in the C-MOVE response. The Number of Completed sub-operations specifies the number of C-STORE sub-operations generated by the requested transfer that have completed successfully. + A C-MOVE response with a status of: + + + Pending shall contain the Number of Completed Sub-operations Attribute + + + Canceled, Warning, Failure, or Success may contain the Number of Completed Sub-operations Attribute + + +
+
+ Number of Failed Sub-Operations + Inclusion of the Number of Failed Sub-operations is conditional based upon the status in the C-MOVE response. The Number of Failed sub-operations specifies the number of C-STORE sub-operations generated by the requested transfer that have Failed. + A C-MOVE response with a status of: + + + Pending shall contain the Number of Failed Sub-operations Attribute + + + Canceled, Warning, Failure, or Success may contain the Number of Failed Sub-operations Attribute + + +
+
+ Number of Warning Sub-Operations + Inclusion of the Number of Warning Sub-operations is conditional based upon the status in the C-MOVE response. The Number of Warning sub-operations specifies the number of C-STORE sub-operations generated by the requested transfer that had a status of warning. + A C-MOVE response with a status of: + + + Pending shall contain the Number of Warnings Sub-operations Attribute + + + Canceled, Warning, Failure, or Success may contain the Number of Warning Sub-operations Attribute + + +
+
+
+ C-MOVE SCU Behavior + This Section discusses both the baseline and extended behavior of the C-MOVE SCU. +
+ Baseline Behavior of SCU + An SCU conveys the following semantics with a C-MOVE request: + + + The SCU shall supply a single value in the Unique Key Attribute for each level above the Query/Retrieve level. For the level of retrieve, the SCU shall supply a single value for one unique key if the level of retrieve is above the STUDY level and shall supply one UID, or a list of UIDs if a retrieval of several items is desired and the retrieve level is STUDY, SERIES or IMAGE. The SCU shall also supply a move destination. The move destination shall be the DICOM Application Entity Title of a DICOM Application Entity capable of serving as the SCP of the Storage Service Class. + + + The SCU shall interpret responses to the C-MOVE with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the number of Remaining, Completed, Failed, and Warning C-STORE sub-operations. + + + The SCU shall interpret responses with a status equal to Success, Warning, Failure, or Refused as final responses. The final response shall indicate the number of Successful C-STORE sub-operations and the number of Failed C-STORE sub-operations resulting from the C-MOVE operation. The SCU shall interpret a status of: + + + Success to indicate that all sub-operations were successfully completed + + + Warning to indicate one or more sub-operations were successfully completed and one or more sub-operations were unsuccessful or had a status of warning, or all sub-operations had a status of warning + + + Failure or Refused to indicate all sub-operations were unsuccessful. + + + + + The SCU may cancel the C-MOVE service by issuing a C-MOVE-CANCEL request at any time during the processing of the C-MOVE. The SCU shall interpret a C-MOVE response with a status of Canceled to indicate the transfer was canceled. The C-MOVE response with a status of Canceled shall contain the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-MOVE-CANCEL request. + + +
+
+ Extended Behavior of SCU + Extended SCU behavior shall be negotiated at Association establishment time. If an option within the extended behavior is not agreed upon in the negotiation, then only baseline SCU behavior shall be performed with respect to that option. Extended SCU behavior includes all baseline behavior with the following option: + + + Relational-retrieve + + + Enhanced Multi-Frame Image Conversion + + + More than one option may be agreed upon. +
+ Relational-Retrieve + The C-MOVE Service with relational-retrieve removes the restriction that the SCU supply Unique Key values for levels above the Query/Retrieve level to identify an entity at the level of the retrieval. Hence, the Identifier of a C-MOVE request may transfer: + + + all Composite Object Instances related to a study by only providing a Study Instance UID (0020,000D) + + + all Composite Object Instances related to a series by only providing a Series Instance UID (0020,000E) + + + individual Composite Object Instances by only providing a list of SOP Instance UIDs (0008,0018) + + +
+
+ Enhanced Multi-Frame Image Conversion + The C-MOVE Service with Enhanced Multi-Frame Image Conversion allows for selection of the default or an alternative view of the instances represented by the Information Model, and hence the retrieval of either the legacy or the converted images, together with any unconverted instances, all of which are required to be processed to maintain referential integrity within the scope of the Patient. + Support for Enhanced Multi-Frame Image Conversion allows the SCU to specify the Attribute Query/Retrieve View (0008,0053) in the Request Identifier with a value of either "CLASSIC" or "ENHANCED". + If Query/Retrieve View (0008,0053) is not present in the Request Identifier, then the SCU requests that the SCP provide all the requested instances it possesses, as received. + If Query/Retrieve View (0008,0053) is present with a value of "CLASSIC", then the SCU requests that the SCP provide all the Classic single frame Instances (converted from Enhanced multi-frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + If Query/Retrieve View (0008,0053) is present with a value of "ENHANCED", then the SCU requests that the SCP provide all the Enhanced multi-frame Instances (converted from Classic single frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + + + + The SCU may assume that no duplicate information will be provided. For example, if an entire series of single frame instances can be converted to a separate series of converted instances, a STUDY level C-MOVE will not provide both series. + + + The Query Information Model is unchanged, and the same unique keys are equally applicable to both views, except that the values for the SERIES and IMAGE level queries will be different and will depend on the converted instance content. + + + The Query/Retrieve View is still required in an IMAGE or SERIES level request identifier, even though the requested unique key(s) are unambiguous, and the view is in a sense "redundant", because the conversion that created the requested instances may not have been executed yet. It is not permitted to specify a view that is inconsistent with the requested unique key(s). + + + +
+
+
+
+ C-MOVE SCP Behavior + This section discusses both the baseline and extended behavior of the C-MOVE SCP. +
+ Baseline Behavior of SCP + An SCP conveys the following semantics with a C-MOVE response: + + + The SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-MOVE request. The SCP shall initiate C-STORE sub-operations for the corresponding storage SOP Instances. These C-STORE sub-operations shall occur on a different Association (that may already exist) from the C-MOVE operation. The SCP of the Query/Retrieve Service Class shall serve as an SCU of the Storage Service Class. + + + The SCP shall either reuse an established and compatible Association or establish a new Association for the C-STORE sub-operations. The SCP shall initiate C-STORE sub-operations over that Association for all stored SOP Instances related to the Patient ID, List of Study Instance UIDs, List of Series Instance UIDs, or List of SOP Instance UIDs depending on the Query/Retrieve level specified in the C-MOVE request. A sub-operation is considered Failed if the SCP is unable to negotiate an appropriate presentation context for a given stored SOP Instance. + + + Optionally, the SCP may generate responses to the C-MOVE with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the Remaining, Completed, Failed, and Warning C-STORE sub-operations. + + + When the number of Remaining sub-operations reaches zero, the SCP shall generate a final response with a status equal to Success, Warning, Failure, or Refused. This response shall indicate the number of Completed sub-operations, the number of Failed sub-operations, and the number of sub-operations with Warning Status. The status contained in the C-MOVE response shall contain: + + + Success if all sub-operations were successfully completed + + + Warning if one or more sub-operations were successfully completed and one or more sub-operations were unsuccessful or had a warning status + + + Warning if all sub-operations had a warning status + + + Failure or Refused if all sub-operations were unsuccessful + + + + + The SCP may receive a C-MOVE-CANCEL request at any time during the processing of the C-MOVE. The SCP shall interrupt all C-STORE sub-operation processing and return a status of Canceled in the C-MOVE response. The C-MOVE response with a status of Canceled shall contain the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-MOVE-CANCEL request. + + + If the SCP manages images in multiple alternate encodings (see ), only one of the alternate encodings of an image shall be included in the set of object instances retrieved by a C-MOVE request at the Patient, Study, or Series level. + + For retrieval of images with alternate encodings using a C-MOVE request at the Patient, Study, or Series level, the SCP may select the appropriately encoded Instance for the retrieval based on identity of the SCU, transfer syntaxes accepted in the C-STORE Association Negotiation, or other factors. + + + + + If the association on which the C-MOVE operation was issued is abnormally terminated, then it will not be possible to issue any further pending responses nor a final response, nor will C-MOVE-CANCEL requests be received. The behavior of the C-MOVE SCP acting as a C-STORE SCU is undefined in this condition. Specifically, whether or not any uncompleted C-STORE sub-operations continue is undefined. + +
+
+ Extended Behavior of SCP + Extended SCP behavior shall be negotiated at Association establishment time. If an option within the extended behavior is not agreed upon in the negotiation, then only baseline SCP behavior shall be performed with respect to that option. Extended SCP behavior includes all baseline behavior with the following option: + + + Relational-retrieve + + + Enhanced Multi-Frame Image Conversion + + + More than one option may be agreed upon. +
+ Relational-Retrieve + The C-MOVE Service with relational-retrieve removes the restriction that the SCU supply Unique Key values for levels above the Query/Retrieve level to help identify an entity at the level of the retrieval. Hence, the Identifier of a C-MOVE request may specify the transfer of: + + + all Composite Object Instances related to a study by only providing a Study Instance UID (0020,000D) + + + all Composite Object Instances related to a series by only providing a Series Instance UID (0020,000E) + + + individual Composite Object Instances by only providing a list of SOP Instance UIDs (0008,0018) + + +
+
+ Enhanced Multi-Frame Image Conversion + If Query/Retrieve View (0008,0053) is not present in the Request Identifier, then the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-MOVE request that correspond to the instances it possesses, as received, and shall initiate C-STORE sub-operations for all the corresponding storage SOP Instances. + If Query/Retrieve View (0008,0053) is present with a value of "CLASSIC", then the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-MOVE request that correspond to the Classic single frame Instances (converted from Enhanced multi-frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted, and shall initiate C-STORE sub-operations for all the corresponding storage SOP Instances. + If Query/Retrieve View (0008,0053) is present with a value of "ENHANCED", then the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-MOVE request that correspond to the Enhanced multi-frame Instances (converted from Classic single frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted, and shall initiate C-STORE sub-operations for all the corresponding storage SOP Instances. + + + + The SCP will not send information that is duplicated to the C-STORE SCP. For example, if an entire series of single frame instances can be converted to a separate series of converted instances, a STUDY level C-MOVE will not send both series. + + + The C-STORE SCP will need to support the necessary SOP Classes for converted instances, otherwise the C-STORE sub-operations will fail in the normal manner and this will be reflected in the C-MOVE responses. + + + The Query Information Model is unchanged, and the same unique, required and optional keys are equally applicable to both views, except that the values for the SERIES and IMAGE level queries will be different and will depend on the converted instance content. + + + The Query/Retrieve View is still required in an IMAGE or SERIES level request identifier, even though the requested unique key(s) are unambiguous. + + + +
+
+
+
+
+ C-GET Operation + SCUs of some SOP Classes of the Query/Retrieve Service Class may generate retrievals using the C-GET operation as described in . The C-GET operation allows an application entity to instruct another application entity to transfer stored SOP Instances to the initiating application entity using the C-STORE operation. Support for the C-GET service shall be agreed upon at Association establishment time by both the SCU and SCP of the C-GET in order for a C-GET operation to occur over the Association. The C-STORE Sub-operations shall be accomplished on the same Association as the C-GET operation. Hence, the SCP of the Query/Retrieve Service Class serves as the SCU of the Storage Service Class. + + The application entity that receives the stored SOP Instances is always the originator of the C-GET operation. + + A C-GET request may be performed to any level of the Query/Retrieve Information Model. However, the transfer of stored SOP Instances may not be performed at this level. The level at which the transfer is performed depends upon the SOP Class. +
+ C-GET Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Query/Retrieve Information Model against which the C-GET is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-GET operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-GET operation and corresponding C-STORE sub-operations with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing, and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. The same priority shall be used for all C-STORE sub-operations. +
+
+ Identifier + The C-GET request shall contain an Identifier. The C-GET response shall conditionally contain an Identifier as required in . + + The Identifier is specified as U in the definition of the C-GET primitive in but is specialized for use with this service. + +
+ Request Identifier Structure + An Identifier in a C-GET request shall contain: + + + Query/Retrieve Level (0008,0052), which defines the level of the retrieval + + + Unique Key Attributes, which may include Patient ID (0010,0020), Study Instance UIDs (0020,000D) Series Instance UIDs (0020,000E), and SOP Instance UIDs (0008,0018) + + + Conditionally, the Attribute Query/Retrieve View (0008,0053). This Attribute may be included if Enhanced Multi-Frame Image Conversion has been accepted during Association Extended Negotiation. It shall not be included otherwise. + + + Specific Character Set (0008,0005) shall be present if Patient ID (0010,0020) is using a character set other than the default character repertoire. + The Unique Keys at each level of the hierarchy and the values allowable for the level of the retrieval shall be defined in the SOP Class definition for the Query/Retrieve Information Model. + + + + In the non-Relational behavior, more than one entity may be retrieved if the Query/Retrieve Level is IMAGE, SERIES or STUDY, using List of UID matching, but only Single Value Matching value may be specified for Patient ID (0010,0020). + + + The issuer of the Patient ID (0010,0020) is implicit; there is no provision to send the Issuer of Patient ID (0010,0021). When there is a possibility of ambiguity of the Patient ID (0010,0020) value, a STUDY level retrieval should be used instead of a PATIENT level retrieval. + + + +
+
+ Response Identifier Structure + The Failed SOP Instance UID List (0008,0058) specifies a list of UIDs of the C-STORE sub-operation SOP Instances for which this C-GET operation has failed. An Identifier in a C-GET response shall conditionally contain the Failed SOP Instance UID List (0008,0058) based on the C-GET response. If no C-STORE sub-operation failed, Failed SOP Instance UID List (0008,0058) is absent and therefore no Data Set shall be sent in the C-GET response. + Specific Character Set (0008,0005) shall not be present. + The Identifier in a C-GET response with a status of: + + + Canceled, Failure, Refused, or Warning shall contain the Failed SOP Instance UID List Attribute + + + Pending shall not contain the Failed SOP Instance UID List Attribute (no Data Set) + + +
+
+
+ Status + + defines the specific status code values that might be returned in a C-GET response. General status code values and fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-GET Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources - Unable to calculate number of matches + + A701 + + (0000,0902) +
+ + Refused: Out of Resources - Unable to perform sub-operations + + A702 + + (0000,1021) + (0000,1022) + (0000,1023) +
+ + Identifier does not match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ + Unable to process + + Cxxx + + (0000,0901) + (0000,0902) +
+ Cancel + + Sub-operations terminated due to Cancel Indication + + FE00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Warning + + Sub-operations Complete - One or more Failures or Warnings + + B000 + + (0000,1021) + (0000,1022) + (0000,1023) +
+ Success + + Sub-operations Complete - No Failures or Warnings + + 0000 + + (0000,1021) + (0000,1022) + (0000,1023) +
+ Pending + + Sub-operations are continuing + + FF00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+
+
+ Number of Remaining Sub-Operations + Inclusion of the Number of Remaining Sub-operations is conditional based upon the status in the C-GET response. The Number of Remaining Sub-operations specifies the number of Remaining C-STORE sub-operations necessary to complete the C-GET operation. + A C-GET response with a status of: + + + Pending shall contain the Number of Remaining Sub-operations Attribute + + + Canceled may contain the Number of Remaining Sub-operations Attribute + + + Warning, Failure, or Success shall not contain the Number of Remaining Sub-operations Attribute. + + +
+
+ Number of Completed Sub-Operations + Inclusion of the Number of Completed Sub-operations is conditional based upon the status in the C-GET response. The Number of Completed Sub-operations specifies the number of C-STORE sub-operations generated by the requested transfer that have completed successfully. + A C-GET response with a status of: + + + Pending shall contain the Number of Completed Sub-operations Attribute + + + Canceled, Warning, Failure, or Success may contain the Number of Completed Sub-operations Attribute + + +
+
+ Number of Failed Sub-Operations + Inclusion of the Number of Failed Sub-operations is conditional based upon the status in the C-GET response. The Number of Failed Sub-operations specifies the number of C-STORE sub-operations generated by the requested transfer that have Failed. + A C-GET response with a status of: + + + Pending shall contain the Number of Failed Sub-operations Attribute + + + Canceled, Warning, Failure, or Success may contain the Number of Failed Sub-operations Attribute + + +
+
+ Number of Warning Sub-Operations + Inclusion of the Number of Warning Sub-operations is conditional based upon the status in the C-GET response. The Number of Warning Sub-operations specifies the number of C-STORE sub-operations generated by the requested transfer that had a status of Warning. + A C-GET response with a status of: + + + Pending shall contain the Number of Warning Sub-operations Attribute + + + Canceled, Warning, Failure, or Success may contain the Number of Warning Sub-operations Attribute + + +
+
+
+ C-GET SCU Behavior + This Section discusses both the baseline and extended behavior of the C-GET SCU. +
+ Baseline Behavior of SCU + An SCU conveys the following semantics with a C-GET request: + + + The SCU shall have proposed sufficient presentation contexts at Association establishment time to accommodate expected C-STORE sub-operations that shall occur over the same Association. The SCU of the Query/Retrieve Service Class shall serve as the SCP of the Storage Service Class. + + + The SCU shall supply a single value in the Unique Key Attribute for each level above the Query/Retrieve level. For the level of retrieve, the SCU shall supply a single value for one unique key if the level of the retrieve is above the STUDY level and shall supply one UID, or a list of UIDs if a retrieval of several items is desired and the retrieve level is STUDY, SERIES or IMAGE. + + + The SCU shall interpret C-GET responses with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the number of Remaining, Completed, Failed, Warning C-STORE sub-operations. + + + The SCU shall interpret a C-GET response with a status equal to Success, Warning, Failure, or Refused as a final response. The final response shall indicate the number of Completed sub-operations and the number of Failed C-STORE sub-operations resulting from the C-GET operation. The SCU shall interpret a status of: + + + Success to indicate that all sub-operations were successfully completed + + + Warning to indicate one or more sub-operations were successfully completed and one or more unsuccessful or all sub-operations had a status of warning + + + Failure or Refused to indicate all sub-operations were unsuccessful + + + + + The SCU may cancel the C-GET operation by issuing a C-GET-CANCEL request at any time during the processing of the C-GET request. A C-GET response with a status of Canceled shall indicate to the SCU that the retrieve was canceled. Optionally, the C-GET response with a status of Canceled shall indicate the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-GET-CANCEL request. + + +
+
+ Extended Behavior of SCU + Extended SCU behavior shall be negotiated at Association establishment time. If an option within the extended behavior is not agreed upon in the negotiation, then only baseline SCU behavior shall be supported with respect to that option. Extended SCU behavior includes all baseline behavior with the following option: + + + Relational-retrieve + + + Enhanced Multi-Frame Image Conversion + + + More than one option may be agreed upon. +
+ Relational-Retrieve + The C-GET Service with relational-retrieve removes the restriction that the SCU supply Unique Key values for levels above the Query/Retrieve level to help identify an entity at the level of the retrieval. Hence, the Identifier of a C-GET request may retrieve: + + + all Composite Object Instances related to a study by providing a Study Instance UID (0020,000D) + + + all Composite Object Instances related to a series by providing a Series Instance UID (0020,000E) + + + individual Composite Object Instances by providing a list of SOP Instance UIDs (0008,0018) + + +
+
+ Enhanced Multi-Frame Image Conversion + The C-GET Service with Enhanced Multi-Frame Image Conversion allows for selection of the default or an alternative view of the instances represented by the Information Model, and hence the retrieval of either the legacy or the converted images, together with any unconverted instances, all of which are required to be processed to maintain referential integrity within the scope of the Patient. + Support for Enhanced Multi-Frame Image Conversion allows the SCU to specify the Attribute Query/Retrieve View (0008,0053) in the Request Identifier with a value of either "CLASSIC" or "ENHANCED". + If Query/Retrieve View (0008,0053) is not present in the Request Identifier, then the SCU requests that the SCP retrieve all the requested instances it possesses, as received. + If Query/Retrieve View (0008,0053) is present with a value of "CLASSIC", then the SCU requests that the SCP retrieve all the Classic single frame Instances (converted from Enhanced multi-frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + If Query/Retrieve View (0008,0053) is present with a value of "ENHANCED", then the SCU requests that the SCP retrieve all the Enhanced multi-frame Instances (converted from Classic single frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted. + + + + The C-GET SCU acting as a C-STORE SCP may assume that no duplicate information will be provided. For example, if an entire series of single frame instances can be converted to a separate series of converted instances, a STUDY level C-GET will not return both series. + + + The C-GET SCU acting as a C-STORE SCP will need to support the necessary SOP Classes for converted instances, otherwise the C-STORE sub-operations will fail in the normal manner and this will be reflected in the C-GET responses. + + + The Query Information Model is unchanged, and the same unique, required and optional keys are equally applicable to both views, except that the values for the SERIES and IMAGE level queries will be different and will depend on the converted instance content. + + + The Query/Retrieve View is still required in an IMAGE or SERIES level request identifier, even though the requested unique key (s) are unambiguous, and the view is in a sense "redundant", because the conversion that created the requested instances may not have been executed yet. It is not permitted to specify a view that is inconsistent with the requested unique key(s). + + + +
+
+
+
+ C-GET SCP Behavior + This Section discusses both the baseline and extended behavior of the C-GET SCP. +
+ Baseline Behavior of SCP + An SCP conveys the following semantics with a C-GET response: + + + The SCP shall identify a set of Entities at the level of the retrieval based upon the values in the Unique Keys in the Identifier of the C-GET request. The SCP shall initiate C-STORE sub-operations for the corresponding storage SOP Instances. The SCP of the Query/Retrieve Service Class shall serve as an SCU of the Storage Service Class. + + + The SCP shall initiate C-STORE sub-operations over the same Association for all stored SOP Instances related to the Patient ID, List of Study Instance UIDs, List of Series Instance UIDs, or List of SOP Instance UIDs depending on the Query/Retrieve level specified in the C-GET request + + + A sub-operation is considered Failed if the SCP is unable to initiate a C-STORE sub-operation because the Query/Retrieve SCU did not offer an appropriate presentation context for a given stored SOP Instance. + + + Optionally, the SCP may generate responses to the C-GET with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the number of Remaining, Completed, Failure, and Warning C-STORE sub-operations. + + + When the number of Remaining sub-operations reaches zero, the SCP shall generate a final response with a status equal to Success, Warning, Failed, or Refused. The status contained in the C-GET response shall contain: + + + Success if all sub-operations were successfully completed + + + Warning if one or more sub-operations were successfully completed and one or more sub-operations were unsuccessful or had a status of warning + + + Warning if all sub-operations had a status of Warning + + + Failure or Refused if all sub-operations were unsuccessful + + + + + The SCP may receive a C-GET-CANCEL request at any time during the processing of the C-GET request. The SCP shall interrupt all C-STORE sub-operation processing and return a status of Canceled in the C-GET response. The C-GET response with a status of Canceled shall contain the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-GET-CANCEL request. + + + If the SCP manages images in multiple alternate encodings (see ), only one of the alternate encodings of an image shall be included in the set of object instances retrieved by a C-GET request at the Patient, Study, or Series level. + + For retrieval of images with alternate encodings using a C-GET request at the Patient, Study, or Series level, the SCP may select the appropriately encoded Instance for the retrieval based on identity of the SCU, transfer syntaxes accepted in the C-STORE Association Negotiation, or other factors. + + + +
+
+ Extended Behavior of SCP + Extended SCP behavior shall be negotiated at Association establishment time. If an option within the extended behavior is not agreed upon in the negotiation, then only baseline SCP behavior shall be performed with respect to that option. Extended SCP behavior includes all baseline behavior with the following option: + + + Relational-retrieve + + + Enhanced Multi-Frame Image Conversion + + + More than one option may be agreed upon. +
+ Relational-Retrieve + The C-GET Service with relational-retrieve removes the restriction that the SCU supply Unique Key values for levels above the Query/Retrieve level to help identify an entity at the level of the retrieval. Hence, the Identifier of a C-GET request may retrieve: + + + all Composite Object Instances related to a study by providing a Study Instance UID + + + all Composite Object Instances related to a series by providing a Series Instance UID + + + individual Composite Object Instances by providing a list of SOP Instance UIDs + + +
+
+ Enhanced Multi-Frame Image Conversion + If Query/Retrieve View (0008,0053) is not present in the Request Identifier, then the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-GET request that correspond to the instances it possesses, as received, and shall initiate C-STORE sub-operations for all the corresponding storage SOP Instances. + If Query/Retrieve View (0008,0053) is present with a value of "CLASSIC", then the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-GET request that correspond to the Classic single frame Instances (converted from Enhanced multi-frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted, and shall initiate C-STORE sub-operations for all the corresponding storage SOP Instances. + If Query/Retrieve View (0008,0053) is present with a value of "ENHANCED", then the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-GET request that correspond to the Enhanced multi-frame Instances (converted from Classic single frame Instances if required), as well as any instances that were converted to preserve referential integrity, and any that did not need to be converted, and shall initiate C-STORE sub-operations for all the corresponding storage SOP Instances. + + + + The C-GET SCP acting as a C-STORE SCU will not send information that is duplicated to the C-GET SCU acting as a C-STORE SCP. For example, if an entire series of single frame instances can be converted to a separate series of converted instances, a STUDY level C-GET will not send both series. + + + The Query Information Model is unchanged, and the same unique, required and optional keys are equally applicable to both views, except that the values for the SERIES and IMAGE level queries will be different and will depend on the converted instance content. + + + The Query/Retrieve View is still required in an IMAGE or SERIES level request identifier, even though the requested unique key(s) are unambiguous. + + + +
+
+
+
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. AEs supporting DICOM Query/Retrieve SOP Classes utilize Association establishment negotiation by defining the use of Application Association Information. See for an overview of Association negotiation. + SOP Classes of the Query/Retrieve Service Class, which include query services based on the C-FIND operation, may use SOP Class Extended Negotiation Sub-Item to negotiate options such as Relational-queries and Enhanced Multi-Frame Image Conversion. + SOP Classes of the Query/Retrieve Service Class, which include retrieval services based on the C-MOVE and C-GET operations, may use the SOP Class Extended Negotiation Sub-Item to negotiate relational-retrieval and Enhanced Multi-Frame Image Conversion. + SOP Classes of the Query/Retrieve Service Class, which include retrieval services based on the C-GET operation, use the SCP/SCU Role Selection Sub-Item to identify the SOP Classes that may be used for retrieval. +
+ Association Negotiation for C-FIND SOP Classes + The following negotiation rules apply to DICOM SOP Classes and Specialized DICOM SOP Classes of the Query/Retrieve Service Class that include the C-FIND operation. + The Association-requester (query SCU role) shall convey in the A-ASSOCIATE request: + + + one Abstract Syntax, in a Presentation Context, for each query based SOP Class supported + + + optionally, one SOP Class Extended Negotiation Sub-Item, for each query based SOP Class + + + The Association-acceptor (query SCP role) of an A-ASSOCIATE request shall accept: + + + one Abstract Syntax, in a Presentation Context, for each query based SOP Class supported + + + optionally, one SOP Class Extended Negotiation Sub-Item, for each query based SOP Class + + +
+ SOP Class Extended Negotiation + The SOP Class Extended Negotiation allows, at Association establishment, peer DICOM AEs to exchange application Association information defined by specific SOP Classes. This is achieved by defining the Service-class-application-information field. The Service-class-application-information field is used to define support for relational-queries, combined date time matching, fuzzy semantic matching of person names, timezone query adjustment and Enhanced Multi-Frame Image Conversion. + This negotiation is optional. If absent, the default conditions shall be: + + + no relational-query support + + + separate (independent) range matching of date and time Attributes + + + literal matching of person names with case sensitivity unspecified + + + timezone query adjustment unspecified + + + no Enhanced Multi-Frame Image Conversion support + + + The Association-requester, for each SOP Class, may use one SOP Class Extended Negotiation Sub-Item. The SOP Class is identified by the corresponding Abstract Syntax Name (as defined by ) followed by the Service-class-application-information field. This field defines one or more sub-fields: + + + relational-query support by the Association-requester + + + combined date and time range matching by the Association-requester + + + literal or fuzzy semantic matching of person names by the Association-requester + + + timezone query adjustment by the Association-requester + + + Enhanced Multi-Frame Image Conversion support by the Association-requester + + + The Association-acceptor shall return a single byte field (single sub-field) if offered a single byte field (single sub-field) by the Association-requester. The Association-acceptor may return either a single byte field (single sub-field) or a multiple byte field if offered a multiple byte field by the Association-requester. A one byte response to a multiple byte request means that the missing sub-fields shall be treated as 0 values. + + The restriction to return only a single byte field if that was all that was offered is because the original DICOM standard only contained one byte and older systems may not be expecting more. + + The Association-acceptor, for each sub-field of the SOP Class Extended Negotiation Sub-Item offered, either accepts the Association-requester proposal by returning the same value (1) or turns down the proposal by returning the value (0). + If the SOP Class Extended Negotiation Sub-Item is not returned by the Association-acceptor then relational-queries are not supported over the Association (default condition). + If the SOP Class Extended Negotiation Sub-Items do not exist in the A-ASSOCIATE indication they shall be omitted in the A-ASSOCIATE response. +
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-RQ) + The SOP Class Extended Negotiation Sub-Item consists of a sequence of mandatory fields as defined by . defines the Service-class-application-information field for DICOM Query/Retrieve SOP Classes and Specialized DICOM Query/Retrieve SOP Classes that include the C-FIND operation. This field may be either one or more bytes in length (i.e., item bytes 2, 3, 4 and 5 are optional). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-RQ
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Relational-queries + + This byte field defines relational-query support by the Association-requester. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - relational queries not supported + 1 - relational queries supported +
+ 2 + + Date-time matching + + This byte field defines whether or not combined date and time Attribute range matching is requested by the Association-requester. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - combined matching not requested + 1 - combined matching requested +
+ 3 + + Fuzzy semantic matching of person names + + This byte field defines whether or not fuzzy semantic person name Attribute matching is requested by the Association-requester. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - fuzzy semantic matching not requested + 1 - fuzzy semantic matching requested +
+ 4 + + Timezone query adjustment + + This byte field defines whether or not the Attribute Timezone Offset From UTC (0008,0201) shall be used to adjust the query meaning for time and datetime fields in queries. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Timezone query adjustment not requested + 1 - Timezone query adjustment requested +
+ 5 + + Enhanced Multi-Frame Image Conversion + + This byte field defines whether or not the Attribute Query/Retrieve View (0008,0053) shall be used to adjust the view returned in queries to consider conversion to or from Enhanced Multi-Frame Images. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Query/Retrieve View not supported + 1 - Query/Retrieve View supported +
+
+
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-AC) + The SOP Class Extended Negotiation Sub-Item is made of a sequence of mandatory fields as defined by . defines the Service-class-application-information field for DICOM Query/Retrieve SOP Classes and Specialized DICOM Query/Retrieve SOP Classes that include the C-FIND operation. This field may be either one or more bytes in length (i.e., item bytes 2, 3, 4 and 5 are optional). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-AC
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Relational-queries + + This byte field defines relational-query support for the Association-acceptor. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - relational-queries not supported + 1 - relational-queries supported +
+ 2 + + Date-time matching + + This byte field defines whether or not combined date and time Attribute range matching will be performed by the Association-acceptor. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - combined matching not performed + 1 - combined matching performed +
+ 3 + + Fuzzy semantic matching of person names + + This byte field defines whether or not fuzzy semantic person name Attribute matching will be performed by the Association-acceptor. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - fuzzy semantic matching not performed + 1 - fuzzy semantic matching performed +
+ 4 + + Timezone query adjustment + + This byte field defines whether or not the Attribute Timezone Offset From UTC (0008,0201) shall be used to adjust the query meaning for time and datetime fields in queries. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Timezone adjustment of queries not performed + 1 - Timezone adjustment of queries performed +
+ 5 + + Enhanced Multi-Frame Image Conversion + + This byte field defines whether or not the Attribute Query/Retrieve View (0008,0053) shall be used to adjust the view returned in queries to consider conversion to or from Enhanced Multi-Frame Images. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Query/Retrieve View not supported + 1 - Query/Retrieve View supported +
+
+
+
+
+ Association Negotiation for C-MOVE SOP Classes + The following negotiation rules apply to DICOM SOP Classes and Specialized DICOM SOP Classes of the Query/Retrieve Service Class that include the C-MOVE operation. + The Association-requester (retrieval SCU role) shall convey in the A-ASSOCIATE request: + + + one Abstract Syntax, in a Presentation Context, for each retrieval based SOP Class supported + + + optionally, one SOP Class Extended Negotiation Sub-Item, for each retrieval based SOP Class + + + The Association-acceptor (retrieval SCP role) of an A-ASSOCIATE request shall accept: + + + one Abstract Syntax, in a Presentation Context, for each retrieval based SOP Class supported + + + optionally, one SOP Class Extended Negotiation Sub-Item, for each retrieval based SOP Class + + +
+ SOP Class Extended Negotiation + The SOP Class Extended Negotiation allows, at Association establishment, peer DICOM AEs to exchange application Association information defined by specific SOP Classes. This is achieved by defining the Service-class-application-information field. The Service-class-application-information field is used to define support for relational-retrievals. + This negotiation is optional. If absent, the default condition shall be: + + + no relational-retrieval support + + + no Enhanced Multi-Frame Image Conversion support + + + The Association-requester, for each SOP Class, may use one SOP Class Extended Negotiation Sub-Item. The SOP Class is identified by the corresponding Abstract Syntax Name (as defined by ) followed by the Service-class-application-information field. This field defines: + + + relational-retrieval support by the Association-requester + + + Enhanced Multi-Frame Image Conversion support by the Association-requester + + + The Association-acceptor shall return a single byte field (single sub-field) if offered a single byte field (single sub-field) by the Association-requester. The Association-acceptor may return either a single byte field (single sub-field) or a multiple byte field if offered a multiple byte field by the Association-requester. A one byte response to a multiple byte request means that the missing sub-fields shall be treated as 0 values. + + The restriction to return only a single byte field if that was all that was offered is because the original DICOM standard only contained one byte and older systems may not be expecting more. + + The Association-acceptor, for each SOP Class Extended Negotiation Sub-Item offered, either accepts the Association-requester proposal by returning the same value (1) or turns down the proposal by returning the value (0). + If the SOP Class Extended Negotiation Sub-Item is not returned by the Association-acceptor then relational-retrievals and Enhanced Multi-Frame Image Conversion are not supported (default condition). + If the SOP Class Extended Negotiation Sub-Items do not exist in the A-ASSOCIATE indication they shall be omitted in the A-ASSOCIATE response. +
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-RQ) + The SOP Class Extended Negotiation Sub-Item consists of a sequence of mandatory fields as defined by . defines the Service-class-application-information field for DICOM Query/Retrieve SOP Classes and Specialized DICOM Query/Retrieve SOP Classes that include the C-MOVE and C-GET operations. + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-RQ
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Relational-retrieval + + This byte field defines relational-retrieval support by the Association-requester. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - relational-retrieval not supported + 1 - relational-retrieval supported +
+ 2 + + Enhanced Multi-Frame Image Conversion + + This byte field defines whether or not the Attribute Query/Retrieve View (0008,0053) shall be used to adjust the view returned in queries to consider conversion to or from Enhanced Multi-Frame Images. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Query/Retrieve View not supported + 1 - Query/Retrieve View supported +
+
+
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-AC) + The SOP Class Extended Negotiation Sub-Item consists of a sequence of mandatory fields as defined by . defines the Service-class-application-information field for DICOM Query/Retrieve SOP Classes and Specialized DICOM Query/Retrieve SOP Classes that include the C-MOVE and C-GET operations. + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-AC
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Relational-retrieval + + This byte field defines relational-retrieval support for the Association-acceptor. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - relational-retrievals not supported + 1 - relational-retrievals supported +
+ 2 + + Enhanced Multi-Frame Image Conversion + + This byte field defines whether or not the Attribute Query/Retrieve View (0008,0053) shall be used to adjust the view returned in queries to consider conversion to or from Enhanced Multi-Frame Images. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Query/Retrieve View not supported + 1 - Query/Retrieve View supported +
+
+
+
+
+ Association Negotiation for C-GET SOP Classes + When an SCP performs the C-GET operation it induces a C-STORE operation for the purpose of transmitting composite SOP Instances for Storage. This induced C-STORE operation (called a sub-operation) requires a switch from the C-GET Presentation Context to a Presentation Context that supports the specific C-STORE sub-operation. + The following negotiation rules apply to retrieval based DICOM Query/Retrieve SOP Classes and Specialized DICOM Query/Retrieve SOP Classes that include the C-GET operation. + The Association-requester (retrieve SCU role) in the A-ASSOCIATE request shall convey: + + + C-GET operation support with: + + + one Abstract Syntax, in a Presentation Context, for each SOP Class supported + + + and optionally, one SOP Class Extended Negotiation Sub-Item, for each retrieval based SOP Class + + + + + Induced Storage sub-operation support where the SOP Class (in the retrieval SCU role) is acting as a Storage SOP Class in the SCP Role. See . For each supported Storage SOP Class, the A-ASSOCIATE request contains: + + + one Abstract Syntax in a Presentation Context + + + one SCP/SCU Role Selection Negotiation Sub-item with the SCP-role field set to indicate support of the SCP role. The SCP/SCU Role Selection Negotiation shall be used as defined in . + + + + + +
+ An Example of the Sub-Operation SCU/SCP Roles + + + + + + +
+
+ + This negotiation does not place any requirements on the SCU-flag of the SCP/SCU Role Selection Negotiation Sub-Item. It may be set if the Association-requester supports the Storage Service Class in the SCU role. + + The Association-acceptor (retrieve SCP role) in the A-ASSOCIATE response shall convey: + + + C-GET operation support with: + + + one Abstract Syntax, in a Presentation Context, for each SOP Class supported + + + + + Induced Storage sub-operation support where the SOP Class (using the retrieval SCP role) is acting as a Storage SOP Class in the SCU Role. See . For each supported Storage SOP Class, the A-ASSOCIATE response contains both: + + + one Abstract Syntax, in a Presentation Context + + + one SCP/SCU Role Selection Negotiation Sub-item with the SCP-role field set to indicate the acceptance of the Association-requester's support of the SCP role. The SCP/SCU Role Selection Negotiation shall be used as defined in . + + + + + + The negotiation does not place any requirements on the SCU-flag of the SCP/SCU Role Selection Negotiation Sub-Item. It may be set if the Association-acceptor accepts the Storage SCP role. illustrates an example of the retrieve (C-GET) negotiation. + + + illustrates an example of the retrieve (C-GET) negotiation. +
+ SOP Class Extended Negotiation + The SOP Class Extended Negotiation allows, at Association establishment, peer DICOM AEs to exchange application Association information defined by specific SOP Classes. + This is achieved by defining the Service-class-application-information field. The Service-class-application-information field is used to define support for relational-retrievals and alternative views for Enhanced Multi-Frame Image Conversion. + +
+ An Example of the Retrieve (C-GET) Negotiation + + + + + + +
+
+ Extended negotiation for SOP Classes based on the retrieval services that include C-GET operations is identical to the negotiation defined for C-MOVE, which is defined in of this Annex. + Extended negotiation for the SOP Classes of the Storage Service Class (for the C-STORE sub-operation) is defined in . +
+
+
+
+ SOP Class Definitions +
+ Patient Root SOP Class Group + In the Patient Root Query/Retrieve Information Model, the information is arranged into four levels that correspond to one of the four values in element (0008,0052) shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + +
Query/Retrieve Level Values for Patient Root
+ + Query/Retrieve Level + + + + Value in (0008,0052) + +
+ Patient Information + + PATIENT +
+ Study Information + + STUDY +
+ Series Information + + SERIES +
+ Composite Object Instance Information + + IMAGE +
+ + The use of the word "Images" rather than "Composite Object Instances" is historical to allow backward compatibility with previous versions of the standard. It should not be taken to mean that Composite Object Instances of other than image type are not included at the level indicated by the value IMAGE. + +
+ Patient Root Query/Retrieve Information Model +
+ E/R Model + The Patient Root Query/Retrieve Information Model may be represented by the entity relationship diagram shown in . + +
+ Patient Root Query/Retrieve Information Model E/R Diagram + + + + + + +
+
+
+
+ Patient Level + + defines the Attributes at the Patient Query/Retrieve level of the Patient Root Query/Retrieve Information Model. + + + + A description of the Attributes of this Information Model is contained in of this part. + + + Although the Patient ID may not be globally unique, the Study Instance UID is globally unique ensuring that no two studies may be misidentified. The scope of uniqueness of the Patient ID may be specified using the Issuer of Patient ID (0010,0021). + + + Previously, Other Patient IDs (0010,1000) was included in this table. This Attribute have been retired. See PS3.4 2017a. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Patient Level Attributes for the Patient Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Type + +
+ Patient's Name + + (0010,0010) + + R +
+ Patient ID + + (0010,0020) + + U +
+ Issuer of Patient ID + + (0010,0021) + + O +
+ Referenced Patient Sequence + + (0008,1120) + + O +
+ >Referenced SOP Class UID + + (0008,1150) + + O +
+ >Referenced SOP Instance UID + + (0008,1155) + + O +
+ Patient's Birth Date + + (0010,0030) + + O +
+ Patient's Birth Time + + (0010,0032) + + O +
+ Patient's Sex + + (0010,0040) + + O +
+ Other Patient IDs Sequence + + (0010,1002) + + O +
+ Other Patient Names + + (0010,1001) + + O +
+ Ethnic Group + + (0010,2160) + + O +
+ Patient Comments + + (0010,4000) + + O +
+ Number of Patient Related Studies + + (0020,1200) + + O +
+ Number of Patient Related Series + + (0020,1202) + + O +
+ Number of Patient Related Instances + + (0020,1204) + + O +
+ + All other Attributes at Patient Level + + + + O +
+
+
+ Study Level + + defines the keys at the Study Information level of the Patient Root Query/Retrieve Information Model. + + + + A description of the Attributes of this Information Model is contained in of this Part. + + + Although the Patient ID may not be globally unique, the Study Instance UID is globally unique ensuring that no two studies may be misidentified. The scope of uniqueness of the Patient ID may be specified using the Issuer of Patient ID (0010,0021). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Study Level Keys for the Patient Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Type + +
+ Study Date + + (0008,0020) + + R +
+ Study Time + + (0008,0030) + + R +
+ Accession Number + + (0008,0050) + + R +
+ Study ID + + (0020,0010) + + R +
+ Study Instance UID + + (0020,000D) + + U +
+ Modalities in Study + + (0008,0061) + + O +
+ SOP Classes in Study + + (0008,0062) + + O +
+ Referring Physician's Name + + (0008,0090) + + O +
+ Study Description + + (0008,1030) + + O +
+ Procedure Code Sequence + + (0008,1032) + + O +
+ >Code Value + + (0008,0100) + + O +
+ >Coding Scheme Designator + + (0008,0102) + + O +
+ >Coding Scheme Version + + (0008,0103) + + O +
+ >Code Meaning + + (0008,0104) + + O +
+ Name of Physician(s) Reading Study + + (0008,1060) + + O +
+ Admitting Diagnoses Description + + (0008,1080) + + O +
+ Referenced Study Sequence + + (0008,1110) + + O +
+ >Referenced SOP Class UID + + (0008,1150) + + O +
+ >Referenced SOP Instance UID + + (0008,1155) + + O +
+ Patient's Age + + (0010,1010) + + O +
+ Patient's Size + + (0010,1020) + + O +
+ Patient's Weight + + (0010,1030) + + O +
+ Occupation + + (0010,2180) + + O +
+ Additional Patient History + + (0010,21B0) + + O +
+ Other Study Numbers + + (0020,1070) + + O +
+ Number of Study Related Series + + (0020,1206) + + O +
+ Number of Study Related Instances + + (0020,1208) + + O +
+ + All other Attributes at Study Level + + + + O +
+
+
+ Series Level + + defines the keys at the Series Information level of the Patient Root Query/Retrieve Information Model. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Series Level Attributes for the Patient Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Type + +
+ Modality + + (0008,0060) + + R +
+ Series Number + + (0020,0011) + + R +
+ Series Instance UID + + (0020,000E) + + U +
+ Number of Series Related Instances + + (0020,1209) + + O +
+ + All Other Attributes at Series Level + + + + O +
+ + The Attribute Number of Series Related Instances is an optional key. It is, however recognized as a broadly needed key and return Attribute, which SCPs are strongly encouraged to support. + +
+
+ Composite Object Instance Level + + defines the keys at the Composite Object Instance Information level of the Patient Root Query/Retrieve Information Model. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Composite Object Instance Level Keys for the Patient Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Type + +
+ Instance Number + + (0020,0013) + + R +
+ SOP Instance UID + + (0008,0018) + + U +
+ SOP Class UID + + (0008,0016) + + O +
+ Alternate Representation Sequence + + (0008,3001) + + O +
+ >Series Instance UID + + (0020,000E) + + O +
+ >SOP Class UID + + (0008,1150) + + O +
+ >SOP Instance UID + + (0008,1155) + + O +
+ >Purpose of Reference Code Sequence + + (0040,A170) + + O +
+ >>Code Value + + (0008,0100) + + O +
+ >>Coding Scheme Designator + + (0008,0102) + + O +
+ >>Coding Scheme Version + + (0008,0103) + + O +
+ >>Code Meaning + + (0008,0104) + + O +
+ Related General SOP Class UID + + (0008,001A) + + O +
+ Concept Name Code Sequence + + (0040,A043) + + O +
+ >Code Value + + (0008,0100) + + O +
+ >Coding Scheme Designator + + (0008,0102) + + O +
+ >Coding Scheme Version + + (0008,0103) + + O +
+ >Code Meaning + + (0008,0104) + + O +
+ Content Template Sequence + + (0040,A504) + + O +
+ >Template Identifier + + (0040,DB00) + + O +
+ >Mapping Resource + + (0008,0105) + + O +
+ Container Identifier + + (0040,0512) + + O +
+ Specimen Description Sequence + + (0040,0560) + + O +
+ >Specimen Identifier + + (0040,0551) + + O +
+ >Specimen UID + + (0040,0554) + + O +
+ + All Other Attributes at Composite Object Instance Level + + + + O +
+ + + + SOP Class UID (0008,0016) is an optional key, but it is strongly recommended that it always be returned by all SCPs, if matching is requested. + + + The Concept Name Code Sequence (0040,A043) and Content Template Sequence (0040,A504) are optional keys that are useful for identifying instances of various Structured Reporting Storage SOP Classes. It is strongly recommended that these keys be supported by the SCP for query against such instances. + + + +
+ Alternate Representation Sequence + The Alternate Representation Sequence (0008,3001) encodes a reference to an alternate encoding of the composite image identified in the Query response item. This alternate encoding may utilize a different SOP Class or have different image quality characteristics, but it shall be the same image. + + The Alternate Representation Sequence (0008,3001) allows the query response about an original image to reference a lossy compressed version, and vice versa. + + An image may be lossy compressed, e.g., for long-term archive purposes, and its SOP Instance UID changed. An application processing a SOP Instance that references the original image UID, e.g., a Structured Report, may query the C-FIND SCP for the image. The SCP returns a reference to an accessible version of the image even if the original SOP Instance is no longer available. + The Alternate Representation Sequence (0008,3001), if present in a Query Request Identifier, shall be zero-length, or shall contain a single zero-length Item. That is, only Universal Matching is defined for this Attribute. + The Alternate Representation Sequence (0008,3001), if present in the Query Response Identifier, may include zero or more Items. Each Alternate Representation Sequence Item in the Query Response Identifier shall include + + + the Series Instance UID (0020,000E) if the alternately encoded image is in a different Series. + + + the SOP Class UID (0008,0016) and SOP Instance UID (0008,0018) of the alternately encoded image. + + + the Purpose of Reference Code Sequence (0040,A170), which shall describe the nature of the alternate encoding of the image. The Purpose of Reference Code Sequence (0040,A170) shall include only one Item. The Baseline Context Group for this Code Sequence is CID 7205. + + +
+
+
+ Scope of the C-GET and C-MOVE Commands and Sub-Operations + A C-MOVE or C-GET request may be performed to any level of the Query/Retrieve Model. However, the transfer of Stored SOP Instances shall always take place at the Composite Object Instance level. A C-MOVE or C-GET where the Query/Retrieve level is the: + + + PATIENT level indicates that all Composite Object Instances related to a Patient shall be transferred. + + + STUDY level indicates that all Composite Object Instances related to a Study shall be transferred. + + + SERIES level indicates that all Composite Object Instances related to a Series shall be transferred. + + + IMAGE level indicates that selected individual Composite Object Instances shall be transferred. + + + + In the Baseline behavior, more than one entity may be retrieved if the Query/Retrieve Level is IMAGE, SERIES or STUDY, using List of UID matching, but only Single Value Matching value may be specified for Patient ID (0010,0020). + +
+
+
+ Conformance Requirements + An implementation may conform to one of the SOP Classes of the Patient Root SOP Class Group as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group shall support queries against the Query/Retrieve Information Model described in using the baseline C-FIND SCU Behavior described in . + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU shall state in its Conformance Statement whether it supports Optional Keys. If it supports Optional Keys, then it shall list the Optional Keys that it supports. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU shall state in its Conformance Statement whether it may generate Relational-queries. If it supports Relational-queries, then it shall also support extended negotiation of relational-queries. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU shall state in its Conformance Statement whether or not it supports extended negotiation of combined date-time matching and/or fuzzy semantic matching of person names. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU shall support transfers against the Query/Retrieve Information Model described in using the C-MOVE SCU Behavior described in . +
+
+ C-GET SCU Conformance + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU shall support retrievals against the Query/Retrieve Information Model described in using the C-GET SCU Behavior described in . + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCU, which generates retrievals using the C-GET operation, shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group shall support queries against the Query/Retrieve Information Model described in using the C-FIND SCP Behavior described in . + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall state in its Conformance Statement whether it supports Optional Keys. If it supports Optional Keys, then it shall list the Optional Keys that it supports. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall state in its Conformance Statement whether it supports Relational-queries. If it supports Relational-queries, then it shall also support extended negotiation of relational-queries. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall state in its Conformance Statement whether or not it supports extended negotiation of combined date-time matching and/or fuzzy semantic matching of person names. If fuzzy semantic matching of person names is supported, then the mechanism for fuzzy semantic matching shall be specified. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall state in its Conformance Statement whether it supports case-insensitive matching for PN VR Attributes and list Attributes for which this applies. + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when interpreting queries, performing matching and encoding responses. +
+
+ C-MOVE SCP Conformance + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall support transfers against the Query/Retrieve Information Model described in using the C-MOVE SCP Behavior described in . + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP, which generates transfers using the C-MOVE operation shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP shall support retrievals against the Query/Retrieve Information Model described in using the C-GET SCP Behavior described in . + An implementation that conforms to one of the SOP Classes of the Patient Root SOP Class Group as an SCP, which generates retrievals using the C-GET operation, shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+
+ SOP Classes + The SOP Classes in the Patient Root Query SOP Class Group of the Query/Retrieve Service Class identify the Patient Root Query/Retrieve Information Model, and the DIMSE-C operations supported. The Standard SOP Classes are listed in . + + + + + + + + + + + + + + + + + + + + + + +
SOP Classes for Patient Root Query/Retrieve
+ + SOP Class Name + + + + SOP Class UID + +
+ Patient Root Query/Retrieve Information Model - FIND + + 1.2.840.10008.5.1.4.1.2.1.1 +
+ Patient Root Query/Retrieve Information Model - MOVE + + 1.2.840.10008.5.1.4.1.2.1.2 +
+ Patient Root Query/Retrieve Information Model - GET + + 1.2.840.10008.5.1.4.1.2.1.3 +
+
+
+
+ Study Root SOP Class Group + In the Study Root Query/Retrieve Information Model, the information is arranged into three levels that correspond to one of the three values in element (0008,0052) shown in . + + + + + + + + + + + + + + + + + + + + + + +
Query/Retrieve Level Values for Study Root
+ + Query/Retrieve Level + + + + Value in (0008,0052) + +
+ Study Information + + STUDY +
+ Series Information + + SERIES +
+ Composite Object Instance Information + + IMAGE +
+ + The use of the word "Images" rather than "Composite Object Instances" is historical to allow backward compatibility with previous versions of the standard. It should not be taken to mean that Composite Object Instances of other than image type are not included at the level indicated by the value IMAGE. + +
+ Study Root Query/Retrieve Information Model +
+ E/R Model + The Study Root Query/Retrieve Information Model may be represented by the entity relationship diagram shown in . + +
+ Study Root Query/Retrieve Information Model E/R Diagram + + + + + + +
+
+
+
+ Study Level + + defines the keys at the Study Information level of the Study Root Query/Retrieve Information Model. + + + + A description of the Attributes of this Information Model is contained in . + + + Although the Patient ID may not be globally unique, the Study Instance UID is globally unique ensuring that no two studies may be misidentified. The scope of uniqueness of the Patient ID may be specified using the Issuer of Patient ID (0010,0021). + + + Previously, Other Patient IDs (0010,1000) was included in this table. This Attribute have been retired. See PS3.4 2017a. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Study Level Keys for the Study Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Type + +
+ Study Date + + (0008,0020) + + R +
+ Study Time + + (0008,0030) + + R +
+ Accession Number + + (0008,0050) + + R +
+ Patient's Name + + (0010,0010) + + R +
+ Patient ID + + (0010,0020) + + R +
+ Study ID + + (0020,0010) + + R +
+ Study Instance UID + + (0020,000D) + + U +
+ Modalities in Study + + (0008,0061) + + O +
+ SOP Classes in Study + + (0008,0062) + + O +
+ Referring Physician's Name + + (0008,0090) + + O +
+ Study Description + + (0008,1030) + + O +
+ Procedure Code Sequence + + (0008,1032) + + O +
+ >Code Value + + (0008,0100) + + O +
+ >Coding Scheme Designator + + (0008,0102) + + O +
+ >Coding Scheme Version + + (0008,0103) + + O +
+ >Code Meaning + + (0008,0104) + + O +
+ Name of Physician(s) Reading Study + + (0008,1060) + + O +
+ Admitting Diagnoses Description + + (0008,1080) + + O +
+ Referenced Study Sequence + + (0008,1110) + + O +
+ >Referenced SOP Class UID + + (0008,1150) + + O +
+ >Referenced SOP Instance UID + + (0008,1155) + + O +
+ Referenced Patient Sequence + + (0008,1120) + + O +
+ >Referenced SOP Class UID + + (0008,1150) + + O +
+ >Referenced SOP Instance UID + + (0008,1155) + + O +
+ Issuer of Patient ID + + (0010,0021) + + O +
+ Patient's Birth Date + + (0010,0030) + + O +
+ Patient's Birth Time + + (0010,0032) + + O +
+ Patient's Sex + + (0010,0040) + + O +
+ Other Patient IDs Sequence + + (0010,1002) + + O +
+ Other Patient Names + + (0010,1001) + + O +
+ Patient's Age + + (0010,1010) + + O +
+ Patient's Size + + (0010,1020) + + O +
+ Patient's Weight + + (0010,1030) + + O +
+ Ethnic Group + + (0010,2160) + + O +
+ Occupation + + (0010,2180) + + O +
+ Additional Patient History + + (0010,21B0) + + O +
+ Patient Comments + + (0010,4000) + + O +
+ Other Study Numbers + + (0020,1070) + + O +
+ Number of Study Related Series + + (0020,1206) + + O +
+ Number of Study Related Instances + + (0020,1208) + + O +
+ + All other Attributes at Study Level + + + + O +
+ + The use of the word "Images" rather than "Composite Object Instances" is historical, and should not be taken to mean that Composite Object Instances of other than image type are not included in the number. + +
+
+ Series Level + Attributes for the Series Level of the Study Root Query/Retrieve Information Model are the same as the Attributes for the Series Level of the Patient Root Query/Retrieve Information Model described in . +
+
+ Composite Object Instance Level + Attributes for the Composite Object Instance Level of the Study Root Query/Retrieve Information Model are the same as the Attributes for the Composite Object Instance Level of the Patient Root Query/Retrieve Information Model described in . +
+
+ Scope of the Get and Move Commands and Sub-Operations + A C-MOVE or C-GET request may be performed to any level of the Query/Retrieve Model. However, the transfer of Stored SOP Instances shall always take place at the Composite Object Instance level. A C-MOVE or C-GET where the Query/Retrieve level is the: + + + STUDY level indicates that all Composite Object Instances related to a Study shall be transferred + + + SERIES level indicates that all Composite Object Instances related to a Series shall be transferred + + + IMAGE level indicates that selected individual Composite Object Instances shall be transferred + + + + In the Baseline behavior, more than one entity may be retrieved if the Query/Retrieve Level is IMAGE, SERIES or STUDY, using List of UID matching, + +
+
+
+ Conformance Requirements + An implementation may conform to one of the SOP Classes of the Study Hierarchy SOP Class Group as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group shall support queries against the Query/Retrieve Information Model described in using the C-FIND SCU behavior described in . + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall state in its Conformance Statement whether it supports Optional Keys. If it supports Optional Keys, then it shall list the Optional Keys that it supports. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall be capable of generating queries using the Hierarchical Search. It shall not generate queries using Relational-queries unless the Relational-queries option has been successfully negotiated. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall state in its Conformance Statement whether it may generate Relational-queries. If it supports Relational Search, then it shall also support extended negotiation of relational-queries. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall state in its Conformance Statement whether or not it supports extended negotiation of combined date-time matching and/or fuzzy semantic matching of person names. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall support transfers against the Query/Retrieve Information Model described in using the C-MOVE SCU Behavior described in . +
+
+ C-GET SCU Conformance + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU shall support retrievals against the Query/Retrieve Information Model described in using the C-GET SCU Behavior described in . + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCU, which generates retrievals using the C-GET operation shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group shall support queries against the Query/Retrieve Information Model described in using the C-FIND SCP behavior described in . + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall state in its Conformance Statement whether it supports Optional Keys. If it supports Optional Keys, then it shall list the Optional Keys that it supports. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall state in its Conformance Statement whether it supports Relational Search. If it supports Relational Search, then it shall also support extended negotiation of relational-queries. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall state in its Conformance Statement whether or not it supports extended negotiation of combined date-time matching and/or fuzzy semantic matching of person names. If fuzzy semantic matching of person names is supported, then the mechanism for fuzzy semantic matching shall be specified. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall state in its Conformance Statement whether it supports case-insensitive matching for PN VR Attributes and list Attributes for which this applies. + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when interpreting queries, performing matching and encoding responses. +
+
+ C-MOVE SCP Conformance + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall support transfers against the Query/Retrieve Information Model described in using the C-MOVE SCP Behavior described in . + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP, which generates transfers using the C-MOVE operation shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP shall support retrievals against the Query/Retrieve Information Model described in using the C-GET SCP Behavior described in . + An implementation that conforms to one of the SOP Classes of the Study Root SOP Class Group as an SCP, which generates retrievals using the C-GET operation shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+
+ SOP Classes + The SOP Classes in the Study Root SOP Class Group of the Query/Retrieve Service Class identify the Study Root Query/Retrieve Information Model, and the DIMSE-C operations supported. The Standard SOP Classes are listed in . + + + + + + + + + + + + + + + + + + + + + + +
SOP Classes for Study Root Query/Retrieve
+ + SOP Class Name + + + + SOP Class UID + +
+ Study Root Query/Retrieve Information Model - FIND + + 1.2.840.10008.5.1.4.1.2.2.1 +
+ Study Root Query/Retrieve Information Model - MOVE + + 1.2.840.10008.5.1.4.1.2.2.2 +
+ Study Root Query/Retrieve Information Model - GET + + 1.2.840.10008.5.1.4.1.2.2.3 +
+
+
+
+ Patient/Study Only SOP Class Group + Retired. See PS 3.4-2004. +
+
+
+ + Study Content Notification Service Class (Normative) + Retired. See PS 3.4-2004. + + + Patient Management Service Class (Normative) + Retired. See PS 3.4-2004. + + + Procedure Step SOP Classes (Normative) +
+ Overview + This Annex defines the Procedure Step SOP Classes. + + This Annex formerly defined a Study Management Service Class that has been retired. See PS 3.4-2004. + +
+ Scope + Retired. See PS 3.4-2004. +
+
+ Study Management Functional Model + Retired. See PS 3.4-2004. +
+
+ Study Management Information Model + Retired. See PS 3.4-2004. +
+
+ Study Management States + Retired. See PS 3.4-2004. +
+
+ Modality Performed Procedure Step Management States + The state information related to the Modality Performed Procedure Step is specified by the Modality Performed Procedure Step IOD in the Attribute Performed Procedure Step Status (0040,0252). + The Performed Procedure Step Object represents only the "performed" segment of the real-world procedure step and not the "scheduled" segment. The number of events is therefore limited; all events are initiated by the modality. The state "DISCONTINUED" means canceled or unsuccessfully terminated, which may happen when the performance of a Procedure Step has been started but cannot be finished by the modality. The modality shall convey this state change to the information system (the SCP), to allow the information system to reschedule or cancel the related Procedure Step. The state "COMPLETED" means that the acquisition of Composite SOP Instances has been successfully completed and the SCU has provided all required Attribute values for the Performed Procedure Step. + + describes the valid Modality Performed Procedure Step states. + + + + + + + + + + + + + + + + + + + + + + +
Modality Performed Procedure Step States
+ + State + + + + Description + +
+ In Progress + + Modality Performed Procedure Step created and execution in progress +
+ Discontinued + + Execution of Modality Performed Procedure Step canceled by modality +
+ Completed + + Modality Performed Procedure Step completed +
+ + defines the valid state transitions for the Performed Procedure Steps. For each of the above defined states the valid state resulting from the occurrence of events is specified. These state transitions are managed by the Modality Performed Procedure Step SOP Class. + + + + + + + + + + + + + + + + + + + + + + + +
Modality Performed Procedure Step State Transition Diagram
+ + States +
+ Events + + In Progress + + Discontinued + + Completed +
+ Performed Procedure Step Discontinued + + Discontinued + + +
+ Performed Procedure Step Completed + + Completed + + +
+
+
+ General Purpose Scheduled Procedure Step Management States (Retired) + Retired. See PS 3.4-2011. +
+
+ General Purpose Performed Procedure Step Management States (Retired) + Retired. See PS 3.4-2011. +
+
+
+ Conformance Overview + The application-level services addressed by this Service Class Definition are specified via the following distinct SOP Classes: + + + Modality Performed Procedure Step SOP Class + + + Modality Performed Procedure Step Notification SOP Class + + + Modality Performed Procedure Step Retrieve SOP Class + + + Each SOP Class operates on a subset of the Modality Performed Procedure Step IOD and specifies the Attributes, operations, notifications, and behavior applicable to the SOP Class. Conformance of Application Entities shall be defined by selecting one or more of the Study and Study Component Management SOP and Meta SOP Classes. For each SOP Class conformance requirements shall be specified in terms of the Service Class Provider (SCP) and the Service Class User (SCU). +
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation procedure specified in shall be used to negotiate the supported SOP Classes. + Support for the SCP/SCU role selection negotiation is mandatory. The SOP Class Extended Negotiation shall not be supported. + + Event notification is a process that logically extends across multiple Associations. SCP implementations should support a local table of SCUs to which event notifications are to be sent. + +
+
+
+ Detached Study Management SOP Class(Retired) + Retired. See PS 3.4-2004. +
+
+ Study Component Management SOP Class(Retired) + Retired. See PS 3.4-2004. +
+
+ Study Management Meta SOP Class(Retired) + Retired. See PS 3.4-2004. +
+
+ Specialized SOP Class Conformance(Retired) + Retired. See PS 3.4-2004. +
+
+ Modality Performed Procedure Step SOP Class +
+ DIMSE Service Group + The DIMSE Services shown in are applicable to the Modality Performed Procedure Step IOD under the Modality Performed Procedure Step SOP Class. + + + + + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-SET + + M/M +
+ The DIMSE Services and Protocols are specified in + +
+
+ Operations + The Application Entity that claims conformance to this SOP Class as an SCU shall be permitted to invoke the following operations and the Application Entity that claims conformance as an SCP shall be capable of providing the following operations. +
+ Create Modality Performed Procedure Step SOP Instance + This operation allows an SCU to create an instance of the Modality Performed Procedure Step SOP Class and provide information about a specific real-world Performed Procedure Step that is under control of the SCU. This operation shall be invoked through the DIMSE N-CREATE Service. + + The modality should inform the Information System as soon as possible that the performance of the Procedure Step has been started by sending the N-CREATE Service Request. This allows an SCP of the Modality Worklist SOP Class (if supported) to update the Modality Worklist. Some of the Attribute values are already known at the beginning of the Procedure Step, they are required to be sent in the N-CREATE command. Other mandatory Attributes are known only at the end of the Performed Procedure Step, they are assigned a value in the N-SET command. + + The same SOP Instance UID is shared by all three Modality Performed Procedure Step SOP Classes. This means that the SOP Instance created and set using the services of the Modality Performed Procedure Step SOP Class can be retrieved using its SOP Instance UID within the service of the Modality Performed Procedure Step Retrieve SOP Class. Changes in its state can be notified by using its SOP Instance UID within the service of the Modality Performed Procedure Step Notification SOP Class. The SOP Class UID specified in the DIMSE N-CREATE and N-SET request primitives shall be the UID of the Modality Performed Procedure Step SOP Class. + The Modality Performed Procedure Step SOP Instance UID shall not be used to identify a SOP Instance of the Study Component Service Class. +
+ Modality Performed Procedure Step Subset Specification + The Application Entity that claims conformance to this SOP Class as an SCU must provide all Required Attributes as specified in . Optional Attributes maintained by the SCP may be provided as well. The Application Entity that claims conformance as an SCP to this SOP Class shall support the subset of the Modality Performed Procedure Step Attributes specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modality Performed Procedure Step SOP Class N-CREATE, N-SET and Final State Attributes
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Requirement Type Final State (see Note 1) + +
+ Specific Character Set + + (0008,0005) + + 1C/1C + (Required if an extended or replacement character set is used) + + 1C/1C + (Required if an extended or replacement character set is used in an Attribute that is set) + +
+ + Performed Procedure Step Relationship + +
+ Scheduled Step Attribute Sequence + + (0040,0270) + + 1/1 + + Not allowed + +
+ >Study Instance UID + + (0020,000D) + + 1/1 + + Not allowed + +
+ >Referenced Study Sequence + + (0008,1110) + + 2/2 + + Not allowed + +
+ >>Referenced SOP Class UID + + (0008,1150) + + 1/1 + + Not allowed + +
+ >>Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + Not allowed + +
+ >Accession Number + + (0008,0050) + + 2/2 + + Not allowed + +
+ >Issuer of Accession Number Sequence + + (0008,0051) + + 3/3 + + Not allowed + +
+ >>Local Namespace Entity ID + + (0040,0031) + + 1C/1C + Required if Universal Entity ID (0040,0032) is not present; may be present otherwise + + Not allowed + +
+ >>Universal Entity ID + + (0040,0032) + + 1C/1C + Required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise. + + Not allowed + +
+ >>Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. + + Not allowed + +
+ >Placer Order Number/Imaging Service Request + + (0040,2016) + + 3/3 + + Not allowed + +
+ >Order Placer Identifier Sequence + + (0040,0026) + + 3/3 + + Not allowed + +
+ >>Local Namespace Entity ID + + (0040,0031) + + 1C/1C + Required if Universal Entity ID (0040,0032) is not present; may be present otherwise + + Not allowed + +
+ >>Universal Entity ID + + (0040,0032) + + 1C/1C + Required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise.. + + Not allowed + +
+ >>Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. + + Not allowed + +
+ >Filler Order Number/Imaging Service Request + + (0040,2017) + + 3/3 + + Not allowed + +
+ >Order Filler Identifier Sequence + + (0040,0027) + + 3/3 + + Not allowed + +
+ >>Local Namespace Entity ID + + (0040,0031) + + 1C/1C + Required if Universal Entity ID (0040,0032) is not present; may be present otherwise + + Not allowed + +
+ >>Universal Entity ID + + (0040,0032) + + 1C/1C + Required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise.. + + Not allowed + +
+ >>Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. + + Not allowed + +
+ >Requested Procedure ID + + (0040,1001) + + 2/2 + + Not allowed + +
+ >Requested Procedure Code Sequence + + (0032,1064) + + 3/3 + + Not allowed + +
+ >>Code Value + + (0008,0100) + + 1/1 + + Not allowed + +
+ >>Coding Scheme Designator + + (0008,0102) + + 1/1 + + Not allowed + +
+ >>Coding Scheme Version + + (0008,0103) + + 3/3 + + Not allowed + +
+ >>Code Meaning + + (0008,0104) + + 1/1 + + Not allowed + +
+ >Requested Procedure Description + + (0032,1060) + + 2/2 + + Not allowed + +
+ >Scheduled Procedure Step ID + + (0040,0009) + + 2/2 + + Not allowed + +
+ >Scheduled Procedure Step Description + + (0040,0007) + + 2/2 + + Not allowed + +
+ >Scheduled Protocol Code Sequence + + (0040,0008) + + 2/2 + + Not allowed + +
+ >>Code Value + + (0008,0100) + + 1/1 + + Not allowed + +
+ >>Coding Scheme Designator + + (0008,0102) + + 1/1 + + Not allowed + +
+ >>Coding Scheme Version + + (0008,0103) + + 3/3 + + Not allowed + +
+ >>Code Meaning + + (0008,0104) + + 3/3 + + Not allowed + +
+ + >>All other Attributes of the Scheduled Protocol Code Sequence + + + + 3/3 + + Not allowed + +
+ Patient's Name + + (0010,0010) + + 2/2 + + Not allowed + +
+ Patient ID + + (0010,0020) + + 2/2 + + Not allowed + +
+ Issuer of Patient ID + + (0010,0021) + + 3/3 + + Not allowed + +
+ Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + 3/3 + + Not allowed + +
+ >Universal Entity ID + + (0040,0032) + + 3/3 + + Not allowed + +
+ >Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. + + Not allowed + +
+ + >All other Attributes of the Issuer of Patient ID Qualifiers Sequence + + + + 3/3 + + Not allowed + +
+ Other Patient IDs Sequence + + (0010,1002) + + 3/3 + + Not allowed + +
+ >Patient ID + + (0010,0020) + + 3/3 + + Not allowed + +
+ >Issuer of Patient ID + + (0010,0021) + + 3/3 + + Not allowed + +
+ >Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + 3/3 + + Not allowed + +
+ + >>All other Attributes of the Issuer of Patient ID Qualifiers Sequence + + + + + 3/3 + + Not allowed + +
+ Patient's Birth Date + + (0010,0030) + + 2/2 + + Not allowed + +
+ Patient's Sex + + (0010,0040) + + 2/2 + + Not allowed + +
+ Referenced Patient Sequence + + (0008,1120) + + 2/2 + + Not allowed + +
+ >Referenced SOP Class UID + + (0008,1150) + + 1/1 + + Not allowed + +
+ >Referenced Instance UID + + (0008,1155) + + 1/1 + + Not allowed + +
+ Admission ID + + (0038,0010) + + 3/3 + + Not Allowed + +
+ Issuer of Admission ID Sequence + + (0038,0014) + + 3/3 + + Not allowed + +
+ >Local Namespace Entity ID + + (0040,0031) + + 1C/1C + Required if Universal Entity ID (0040,0032) is not present; may be present otherwise + + Not allowed + +
+ >Universal Entity ID + + (0040,0032) + + 1C/1C + Required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise.. + + Not allowed + +
+ >Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. + + Not allowed + +
+ Service Episode ID + + (0038,0060) + + 3/3 + + Not allowed + +
+ Issuer of Service Episode ID Sequence + + (0038,0064) + + 3/3 + + Not allowed + +
+ >Local Namespace Entity ID + + (0040,0031) + + 1C/1C + Required if Universal Entity ID (0040,0032) is not present; may be present otherwise + + Not allowed + +
+ >Universal Entity ID + + (0040,0032) + + 1C/1C + Required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise.. + + Not allowed + +
+ >Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. + + Not allowed + +
+ Service Episode Description + + (0038,0062) + + 3/3 + + Not allowed + +
+ + Performed Procedure Step Information + +
+ Performed Procedure Step ID + + (0040,0253) + + 1/1 + + Not allowed + +
+ Performed Station AE Title + + (0040,0241) + + 1/1 + + Not allowed + +
+ Performed Station Name + + (0040,0242) + + 2/2 + + Not allowed + +
+ Performed Location + + (0040,0243) + + 2/2 + + Not allowed + +
+ Performed Procedure Step Start Date + + (0040,0244) + + 1/1 + + Not allowed + +
+ Performed Procedure Step Start Time + + (0040,0245) + + 1/1 + + Not allowed + +
+ Performed Procedure Step Status + + (0040,0252) + + 1/1 + + 3/1 + +
+ Performed Procedure Step Description + + (0040,0254) + + 2/2 + + 3/2 + +
+ Performed Procedure Type Description + + (0040,0255) + + 2/2 + + 3/2 + +
+ Procedure Code Sequence + + (0008,1032) + + 2/2 + + 3/2 + +
+ >Code Value + + (0008,0100) + + 1/1 + + 1/1 + +
+ >Coding Scheme Designator + + (0008,0102) + + 1/1 + + 1/1 + +
+ >Coding Scheme Version + + (0008,0103) + + 3/3 + + 3/3 + +
+ >Code Meaning + + (0008,0104) + + 3/3 + + 3/3 + +
+ Reason For Performed Procedure Code Sequence + + (0040,1012) + + 3/3 + + 3/3 + +
+ >Code Value + + (0008,0100) + + 1/1 + + 1/1 + +
+ >Coding Scheme Designator + + (0008,0102) + + 1/1 + + 1/1 + +
+ >Coding Scheme Version + + (0008,0103) + + 3/3 + + 3/3 + +
+ >Code Meaning + + (0008,0104) + + 1/1 + + 1/1 + +
+ Performed Procedure Step End Date + + (0040,0250) + + 2/2 + + 3/1 + + 1 +
+ Performed Procedure Step End Time + + (0040,0251) + + 2/2 + + 3/1 + + 1 +
+ Comments on the Performed Procedure Step + + (0040,0280) + + 3/3 + + 3/3 + +
+ Performed Procedure Step Discontinuation Reason Code Sequence + + (0040,0281) + + 3/3 + + 3/3 + +
+ >Code Value + + (0008,0100) + + 1/1 + + 1/1 + +
+ >Coding Scheme Designator + + (0008,0102) + + 1/1 + + 1/1 + +
+ >Coding Scheme Version + + (0008,0103) + + 3/3 + + 3/3 + +
+ >Code Meaning + + (0008,0104) + + 3/3 + + 3/3 + +
+ + Image Acquisition Results + +
+ Modality + + (0008,0060) + + 1/1 + + Not allowed + +
+ Study ID + + (0020,0010) + + 2/2 + + Not allowed + +
+ Performed Protocol Code Sequence + + (0040,0260) + + 2/2 + + 3/2 + +
+ >Code Value + + (0008,0100) + + 1/1 + + 1/1 + +
+ >Coding Scheme Designator + + (0008,0102) + + 1/1 + + 1/1 + +
+ >Coding Scheme Version + + (0008,0103) + + 3/3 + + 3/3 + +
+ >Code Meaning + + (0008,0104) + + 3/3 + + 3/3 + +
+ + >All other Attributes of the Performed Protocol Code Sequence + + + + 3/3 + + Not allowed + +
+ Performed Series Sequence + + (0040,0340) + + 2/2 + + 3/1 + + 1 + (see note 2) +
+ >Performing Physician's Name + + (0008,1050) + + 2/2 + + 2/2 + + 2 +
+ >Protocol Name + + (0018,1030) + + 1/1 + + 1/1 + + 1 +
+ >Operators' Name + + (0008,1070) + + 2/2 + + 2/2 + + 2 +
+ >Series Instance UID + + (0020,000E) + + 1/1 + + 1/1 + + 1 +
+ >Series Description + + (0008,103E) + + 2/2 + + 2/2 + + 2 +
+ >Retrieve AE Title + + (0008,0054) + + 2/2 + + 2/2 + + 2 +
+ >Archive Requested + + (0040,A494) + + 3/3 + + 3/3 + +
+ >Referenced Image Sequence + + (0008,1140) + + 2/2 + + 2/2 + + See + +
+ >>Referenced SOP Class UID + + (0008,1150) + + 1/1 + + 1/1 + +
+ >>Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + 1/1 + +
+ >>Container Identifier + + (0040,0512) + + 3/3 + + 3/3 + +
+ >>Specimen Description Sequence + + (0040,0560) + + 3/3 + + 3/3 + +
+ >>>Specimen Identifier + + (0040,0551) + + 1/1 + + 1/1 + +
+ >>>Specimen UID + + (0040,0554) + + 1/1 + + 1/1 + +
+ >Referenced Non-Image Composite SOP Instance Sequence + + (0040,0220) + + 2/2 + + 2/2 + + See + +
+ >>Referenced SOP Class UID + + (0008,1150) + + 1/1 + + 1/1 + +
+ >>Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + 1/1 + +
+ + >All other Attributes of the Performed Series Sequence + + + + 3/3 + + 3/3 + +
+ + All other Attributes of the and + + + + + 3/3 + + 3/3 + +
+ + + + The requirement for the final state is that which applies at the time that the Performed Procedure Step Status (0040,0252) is N-SET to a value of COMPLETED or DISCONTINUED, as described in . It is only described if it is different from the SCP requirement for the N-CREATE. + + + The Performed Series Sequence (0040,0340) may not be empty (zero length) at the time that the Performed Procedure Step Status (0040,0252) is N-SET to a value of COMPLETED or DISCONTINUED. In other words a Series must exist for every Performed Procedure Step, though it may contain no Images or Non-Image Composite objects, if none were created, as described in . + + + Attributes (0040,1006) Placer Order Number/Procedure and (0040,1007) Filler Order Number/Procedure were previously defined in DICOM. They are now retired (see PS3.3-1998). + + + Attributes (0040,2006) and (0040,2007) were previously defined in DICOM. They are now retired (see PS3.3-1998). + + + Only Attributes that are specified in a SOP Instance at N-CREATE may later be updated through the N-SET. If an SCU wishes to use the PPS Discontinuation Reason Code Sequence (0040,0281), it must create that Attribute (zero-length) during MPPS N-CREATE. + + + +
+
+ Service Class User + The SCU shall specify in the N-CREATE request primitive the Class and Instance UIDs of the Modality Performed Procedure Step SOP Instance that is created and for which Attribute Values are to be provided. + + This requirement facilitates the inclusion of relevant Attributes in the Composite SOP Instances generated during the Performed Procedure Step. + + The SCU shall provide Attribute values for the Modality Performed Procedure Step SOP Class Attributes as specified in . Additionally, values may be provided for optional Modality Performed Procedure Step IOD Attributes that are supported by the SCP. The encoding rules for Modality Performed Procedure Step Attributes are specified in the N-CREATE request primitive specification in . + The SCU shall be capable of providing all required Attribute values to the SCP in the N-CREATE request primitive. The SCU may provide Attribute values for optional Attributes that are not maintained by the SCP. In such case the SCU shall function properly regardless of whether the SCP accepts values for those Attributes or not. + All Attributes shall be created before they can be set. Sequence Attributes shall be created before they can be filled. Sequence Item Attributes shall not be created at zero length. + + Not all the Attributes that can be created can be set afterward (see ). + + The SCU shall only send the N-CREATE request primitive with the value for the Attribute "Performed Procedure Step Status" (0040,0252) set to "IN PROGRESS". + + + + It is assumed but not required that the SCU (the modality) received the Study Instance UID within the scope of the Basic Worklist Management SOP Class. + + + If the SCU has grouped multiple Requested Procedures into a single performed step the Study Instance UID (0020,000D) Attribute within the Scheduled Step Attributes Sequence (0040,0270) may be the Study Instance UID (0020,000D) for the study that contains all images and non-image composite instances created during performance of the current step. This value may be generated by the SCU and may be the same for all items of the sequence. In addition, the Referenced Study Sequence (0008,1110) may contain the Study Instance UIDs from the Requested Procedures being grouped. If Referenced Study Sequence (0008,1110) is present with an Item, the SOP Class UID of the Detached Study Management SOP Class (Retired) may be used in Referenced SOP Class UID (0008,1150). + + + If the SCU does not have available Scheduled Procedure Step data applicable to the current step, the SCU may generate a value for the Study Instance UID (0020,000D) Attribute within the Scheduled Step Attributes Sequence (0040,0270). This value of the Study Instance UID (0020,000D) may be stored in all images and non-image composite SOP instances created during performance of this step. All other Attributes within the Scheduled Step Attribute Sequence (0040,0270) may be set to zero length for 2/2 requirement types or absent for 3/3 requirement types (see ). + + + +
+
+ Service Class Provider + The N-CREATE operation allows the SCU to provide to the SCP selected Attribute values for a specific Modality Performed Procedure Step SOP Instance. This operation shall be invoked through the use of the DIMSE N-CREATE Service used in conjunction with the appropriate Modality Performed Procedure Step SOP Instance. + The SCP shall return, via the N-CREATE response primitive, the N-CREATE Response Status Code applicable to the associated request. + The SCP shall accept N-CREATE request primitives only if the value of the Attribute "Performed Procedure Step Status" (0040,0252) is "IN PROGRESS". If the Performed Procedure Step Status Attribute has another value, the SCP shall set the failure status code "Invalid Attribute value" (Code: 0106H) with an Attribute List. + + The SCP may update the scheduling information on which the Modality Worklist is based, including the values of Study Date (0008,0020) and Study Time (0008,0030) using the earliest corresponding values of Performed Procedure Step Date (0040,0244) and Performed Procedure Step Time (0040,0245), in order to achieve consistency of Study level Attributes when multiple procedure steps are performed on different devices. + +
+
+ Status Codes + There are no specific status codes. See for response status codes. +
+
+
+ Set Modality Performed Procedure Step Information + This operation allows an SCU to set Attribute Values of an instance of the Modality Performed Procedure Step SOP Class and provide information about a specific real-world Modality Performed Procedure Step that is under control of the SCU. This operation shall be invoked through the DIMSE N-SET Service. +
+ Modality Performed Procedure Step IOD Subset Specification + The Application Entity that claims conformance to this SOP Class as an SCU may choose to modify a subset of the Attributes maintained by the SCP. The Application Entity that claims conformance as an SCP to this SOP Class shall support the subset of the Modality Performed Procedure Step Attributes specified in . + The character set used for Attribute Values updated using the N-SET shall be the same as that specified by the N-CREATE Request Primitive. +
+
+ Service Class User + The SCU shall specify in the N-SET request primitive the UID of the Modality Performed Procedure Step SOP Instance for which it wants to set Attribute Values. + The SCU shall be permitted to set Attribute values for any Modality Performed Procedure Step SOP Class Attribute specified in . The SCU shall specify the list of Modality Performed Procedure Step SOP Class Attributes for which it wants to set the Attribute Values. The SCU shall provide, with one or more N-SET request primitives, the Attribute values specified in . The encoding rules for Modality Performed Procedure Step Attributes are specified in the N-SET request primitive specification in . The SCU shall only set Attribute Values that are already created with an N-CREATE request. + The SCU shall not send N-SET request primitives for a Modality Performed Procedure Step SOP Instance after a N-SET request primitive with a value for the Attribute "Performed Procedure Step Status" (0040,0252) is "COMPLETED" or "DISCONTINUED" has been sent. + If Sequences are included in a N-SET command, all Items of a Sequence are to be included in the command and not only the Items to be updated. + Once the Modality Performed Procedure Step Status (0040,0252) has been set to "COMPLETED" or "DISCONTINUED" the SCU shall no longer modify the Modality Performed Procedure Step SOP Instance, and shall not create new Composite SOP Instances as part of the same Modality Performed Procedure Step SOP Instance. + + A Modality that wishes to continue or resume creating Composite SOP Instances may create a new Modality Performed Procedure Step. + + Before or when Modality Performed Procedure Step Status (0040,0252) is set to "COMPLETED" or "DISCONTINUED" the SCU shall have created or set all the Attributes according to the requirements in the Final State column of . + Before or when Modality Performed Procedure Step Status (0040,0252) is set to "COMPLETED" or "DISCONTINUED" the SCU shall have sent to the SCP a list of all Image SOP Instances and all Non-Image Composite SOP Instances created during the Procedure Step in Referenced Image Sequence (0008,1140) and Referenced Non-Image Composite SOP Instance Sequence (0040,0220) respectively. + + + + The intent is that a completed or discontinued Modality Performed Procedure Step entity will contain a complete list of all the Images and Non-Image Composite SOP Instances that were created. + + + The distinction between the list of images and non-images is present for historic reasons only, and has no semantic significance. + + + + The Modality Performed Procedure Step Status (0040,0252) shall not be set to "COMPLETED" or "DISCONTINUED" if the list contains neither Image references nor Non-Image Composite SOP Instance references, unless no such Instances were created. +
+
+ Service Class Provider + The N-SET operation allows the SCU to request that the SCP update selected Attribute values for a specific Modality Performed Procedure Step SOP Instance. This operation shall be invoked through the use of the DIMSE N-SET Service used in conjunction with the appropriate Modality Performed Procedure Step SOP Instance. The N-SET value for Specific Character Set (0008,0005) does not replace the previous value. The SCP shall appropriately modify its internal representation so that subsequent operations reflect the combination of the character sets in use by the Attributes in this N-SET and those used by Attributes that have not been modified. + + The SCP may need to convert the text for instance to the Unicode character set. If the SCP is not able to perform a necessary conversion it may return the Invalid Attribute value error code (0106H). + + The SCP shall return, via the N-SET response primitive, the N-SET Response Status Code applicable to the associated request. Contingent on the N-SET Response Status, the SCP shall update the Referenced Performed Procedure Step Attributes. + The SCP shall accept N-SET request primitives only if the value of the already existing Attribute "Performed Procedure Step Status" (0040,0252) is "IN PROGRESS". If the already existing Performed Procedure Step Status Attribute has another value, the SCP shall set the failure status code "Processing failure" (Code: 0110H) with a Specific Error Comment (see ). + The SCP may itself modify any Attributes of the Modality Performed Procedure Step SOP Instance only after the "Performed Procedure Step Status" (0040,0252) has been set to "COMPLETED" or "DISCONTINUED". + + + + Such coercion of Attributes by the SCP may be necessary to correct, for example, patient identification information or incorrectly selected scheduling information. Such an operation is not permitted to the SCU by the requirements described in , which might create a new Modality Performed Procedure Step SOP Instance to achieve the same objective. + + + Under exceptional circumstances, it may be necessary for the SCP to itself set the Performed Procedure Step Status (0040,0252) to COMPLETED or DISCONTINUED, for example if the Modality has failed. When the Modality recovers, subsequent N-SETs may fail. + + + +
+
+ Status Codes + The specific error comment that may be returned as a status code in a N-SET-RSP is defined in . See for additional response status codes. + + + + + + + + + + + + + + + + + + + + +
N-SET Status
+ + Service Status + + + + Further Meaning + + + + Status Code + + + + Error Comment (0000,0902) + + + + Error ID (0000,0903) + +
+ Failure + + Processing Failure + + 0110 + + Performed Procedure Step Object may no longer be updated + + A710 +
+
+
+
+
+ Modality Performed Procedure Step SOP Class UID + The Modality Performed Procedure Step SOP Class shall be uniquely identified by the Modality Performed Procedure Step SOP Class UID that shall have the value "1.2.840.10008.3.1.2.3.3". +
+
+ Conformance Requirements + Implementations providing conformance to the Modality Performed Procedure Step SOP Class shall be conformant as described in the following sections and shall include within their Conformance Statement information as described below. + An implementation may conform to this SOP Class as an SCU or as an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that is conformant to this SOP Class as an SCU shall meet conformance requirements for the operations that it invokes. +
+ Operations + Any Attributes for which Attribute Values may be provided (using the N-CREATE Service) by the SCU shall be enumerated in the Conformance Statement. + Any Attributes for which Attribute Values may be provided (using the N-SET Service) by the SCU shall be enumerated in the Conformance Statement. + An implementation that conforms to this SOP Class as an SCU shall specify under which conditions during the performance of the real-world Performed Procedure Step it will create the SOP Class Instance and under which conditions it will set the status value to COMPLETED and DISCONTINUED. + An implementation that conforms to this SOP Class as an SCU shall specify what strategy it applies to group Storage SOP Class Instances referenced in a Performed Procedure Step. + + For example, whether or not Radiation Dose SR instances are sent within the same Performed Procedure Step as the images to which it applies, or a different Performed Procedure Step. See the discussion of the MPPS in the DICOM real-world model in . + +
+
+
+ SCP Conformance + An implementation that is conformant to this SOP Class as an SCP shall meet conformance requirements for the operations that it performs. +
+ Operations + Any Attributes for which Attribute Values may be provided (using the N-CREATE Service) by the SCU shall be enumerated in the Conformance Statement. + Any Attributes for which Attribute Values may be updated (using the N-SET Service) by the SCU shall be enumerated in the Conformance Statement. + The Conformance Statement shall also provide information on the behavior of the SCP at the following occurrences: + + + The creation of a new Instance of the Modality Performed Procedure Step SOP Class with the status "IN PROGRESS". The result of that process on the scheduling information and on the Attributes values of the Modality Worklist SOP Class shall be specified. + + + The update of the Attribute "Performed Procedure Step Status", i.e., the change from the state "IN PROGRESS" to "DISCONTINUED" or to "COMPLETED". + + + Which Attributes the SCP may coerce after the state has been set to "IN PROGRESS" or "DISCONTINUED" or to "COMPLETED". + + + For how long the Modality Performed Procedure Step SOP Instance will persist on the SCP. + + +
+
+
+
+
+ Modality Performed Procedure Step Retrieve SOP Class +
+ DIMSE Service Group + The DIMSE Services shown in are applicable to the Modality Performed Procedure Step IOD under the Modality Performed Procedure Step Retrieve SOP Class. + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-GET + + M/M +
+ The DIMSE Services and Protocols are specified in . If the Modality Performed Procedure Step Object is no longer available the Request Primitive will be answered with a Failure Status message "No Such Object Instance". +
+
+ Operations + The Application Entity that claims conformance to this SOP Class as an SCU shall be permitted to invoke the following operations and the Application Entity that claims conformance as an SCP shall be capable of providing the following operations. +
+ Get Performed Procedure Step Information + This operation allows an SCU to get information about a specific real-world Performed Procedure Step that is represented as a Modality Performed Procedure Step Retrieve SOP Instance by a Modality Performed Procedure Step Retrieve SCP. The operation is performed on a Modality Performed Procedure Step IOD. This operation shall be invoked through the DIMSE N-GET Service used in conjunction with the appropriate Modality Performed Procedure Step Retrieve SOP Instance. + The same SOP Instance UID is shared by all three Modality Performed Procedure Step SOP Classes. This means that the SOP Instance created and set using the services of the Modality Performed Procedure Step SOP Class can be retrieved using its SOP Instance UID within the service of the Modality Performed Procedure Step Retrieve SOP Class. Changes in its state can be notified by using its SOP Instance UID within the service of the Modality Performed Procedure Step Notification SOP Class. The SOP Class UID specified in the DIMSE N-GET request primitive shall be the UID of the Modality Performed Procedure Step Retrieve SOP Class. + The Modality Performed Procedure Retrieve Step SOP Instance UID shall not be used to identify a SOP Instance of the Study Component Service Class. + + An Application Entity may support the SCU role of the Modality Performed Procedure Step Retrieve SOP Class in order to obtain information about Performed Procedure Steps created by other Application Entities. + +
+ Modality Performed Procedure Step Retrieve IOD Subset Specifications + The Application Entity that claims conformance to this SOP Class as an SCU may choose to interpret the Attribute values maintained by the SCP that the SCU receives via the operation of this SOP Class. The Application Entity that claims conformance as an SCP to this Modality Performed Procedure Step Retrieve SOP Class shall support the subset of the Modality Performed Procedure Step Retrieve Attributes specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Modality Performed Procedure Step Retrieve SOP Class N-GET Attributes
+ + Attribute Name + + + + Tag + + + + Requirement Type (SCU/SCP) + +
+ Specific Character Set + + (0008,0005) + + 3/1C + (Required if an extended or replacement character set is used) +
+ + Performed Procedure Step Relationship + +
+ Scheduled Step Attributes Sequence + + (0040,0270) + + 3/1 +
+ >Study Instance UID + + (0020,000D) + + -/1 +
+ >Referenced Study Sequence + + (0008,1110) + + -/2 +
+ >>Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ >>Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ >Accession Number + + (0008,0050) + + -/2 +
+ >Issuer of Accession Number Sequence + + (0008,0051) + + -/3 +
+ >>Local Namespace Entity ID + + (0040,0031) + + -/3 +
+ >>Universal Entity ID + + (0040,0032) + + -/3 +
+ >>Universal Entity ID Type + + (0040,0033) + + -/3 +
+ >Placer Order Number/Imaging Service Request + + (0040,2016) + + -/3 +
+ >Order Placer Identifier Sequence + + (0040,0026) + + -/3 +
+ >>Local Namespace Entity ID + + (0040,0031) + + -/3 +
+ >>Universal Entity ID + + (0040,0032) + + -/3 +
+ >>Universal Entity ID Type + + (0040,0033) + + -/3 +
+ >Filler Order Number/Imaging Service Request + + (0040,2017) + + -/3 +
+ >Order Filler Identifier Sequence + + (0040,0027) + + -/3 +
+ >>Local Namespace Entity ID + + (0040,0031) + + -/3 +
+ >>Universal Entity ID + + (0040,0032) + + -/3 +
+ >>Universal Entity ID Type + + (0040,0033) + + -/3 +
+ >Requested Procedure Code Sequence + + (0032,1064) + + -/3 +
+ >>Code Value + + (0008,0100) + + -/1 +
+ >>Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >>Code Meaning + + (0008,0104) + + -/1 +
+ >Requested Procedure Description + + (0032,1060) + + -/2 +
+ >Requested Procedure ID + + (0040,1001) + + -/2 +
+ >Scheduled Procedure Step ID + + (0040,0009) + + -/2 +
+ >Scheduled Procedure Step Description + + (0040,0007) + + -/2 +
+ >Scheduled Protocol Code Sequence + + (0040,0008) + + -/2 +
+ >>Code Value + + (0008,0100) + + -/1 +
+ >>Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >>Coding Scheme Version + + (0008,0103) + + -/3 +
+ >>Code Meaning + + (0008,0104) + + -/3 +
+ + >>All other Attributes of the Scheduled Protocol Code Sequence + + + + -/3 +
+ Patient's Name + + (0010,0010) + + 3/2 +
+ Patient ID + + (0010,0020) + + 3/2 +
+ Issuer of Patient ID + + (0010,0021) + + 3/3 +
+ Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + 3/3 +
+ >Universal Entity ID + + (0040,0032) + + 3/3 +
+ >Universal Entity ID Type + + (0040,0033) + + 1C/1C + Required if Universal Entity ID (0040,0032) is present. +
+ + >All other Attributes of the Issuer of Patient ID Qualifiers Sequence + + + + 3/3 +
+ >Patient ID + + (0010,0020) + + 3/3 +
+ >Issuer of Patient ID + + (0010,0021) + + 3/3 +
+ >Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + 3/3 +
+ + >>All other Attributes of the Issuer of Patient ID Qualifiers Sequence + + + + + 3/3 +
+ Patient's Birth Date + + (0010,0032) + + 3/2 +
+ Patient's Sex + + (0010,0040) + + 3/2 +
+ Referenced Patient Sequence + + (0008,1120) + + 3/2 +
+ >Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ >Referenced Instance UID + + (0008,1155) + + -/1 +
+ Admission ID + + (0038,0010) + + 3/3 +
+ Issuer of Admission ID Sequence + + (0038,0014) + + 3/3 +
+ >Local Namespace Entity ID + + (0040,0031) + + -/3 +
+ >Universal Entity ID + + (0040,0032) + + -/3 +
+ >Universal Entity ID Type + + (0040,0033) + + -/3 +
+ Service Episode ID + + (0038,0060) + + 3/3 +
+ Issuer of Service Episode ID Sequence + + (0038,0064) + + 3/3 +
+ >Local Namespace Entity ID + + (0040,0031) + + -/3 +
+ >Universal Entity ID + + (0040,0032) + + -/3 +
+ >Universal Entity ID Type + + (0040,0033) + + -/3 +
+ Service Episode Description + + (0038,0062) + + 3/3 +
+ + Performed Procedure Step Information + +
+ Performed Station AE Title + + (0040,0241) + + 3/1 +
+ Performed Station Name + + (0040,0242) + + 3/2 +
+ Performed Location + + (0040,0243) + + 3/2 +
+ Performed Procedure Step Start Date + + (0040,0244) + + 3/1 +
+ Performed Procedure Step Start Time + + (0040,0245) + + 3/1 +
+ Performed Procedure Step ID + + (0040,0253) + + 3/1 +
+ Performed Procedure Step Status + + (0040,0252) + + 3/1 +
+ Performed Procedure Step End Date + + (0040,0250) + + 3/2 +
+ Performed Procedure Step End Time + + (0040,0251) + + 3/2 +
+ Performed Procedure Step Description + + (0040,0254) + + 3/2 +
+ Performed Procedure Type Description + + (0040,0255) + + 3/2 +
+ Procedure Code Sequence + + (0008,1032) + + 3/2 +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/3 +
+ >Code Meaning + + (0008,0104) + + -/3 +
+ Comments on the Performed Procedure Step + + (0040,0280) + + 3/3 +
+ Performed Procedure Step Discontinuation Reason Code Sequence + + (0040,0281) + + 3/2 +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/3 +
+ >Code Meaning + + (0008,0104) + + -/3 +
+ + Image Acquisition Results + +
+ Performed Series Sequence + + (0040,0340) + + 3/2 +
+ >Performing Physician's Name + + (0008,1050) + + -/2 +
+ >Protocol Name + + (0018,1030) + + -/1 +
+ >Operators' Name + + (0008,1070) + + -/2 +
+ >Series Instance UID + + (0020,000E) + + -/1 +
+ >Series Description + + (0008,103E) + + -/2 +
+ >Retrieve AE Title + + (0008,0054) + + -/2 +
+ >Referenced Image Sequence + + (0008,1140) + + -/2 +
+ >>Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ >>Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ >Referenced Non-Image Composite SOP Instance Sequence + + (0040,0220) + + -/2 +
+ >>Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ >>Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ + >All other Attributes of the Performed Series Sequence + + + + -/3 +
+ Modality + + (0008,0060) + + 3/1 +
+ Study ID + + (0020,0010) + + 3/2 +
+ Performed Protocol Code Sequence + + (0040,0260) + + 3/2 +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/3 +
+ >Code Meaning + + (0008,0104) + + -/3 +
+ + >All other Attributes of the Performed Protocol Code Sequence + + + + -/3 +
+ + All other Attributes of the and + + + + + 3/3 +
+ + + + Attributes (0040,1006) Placer Order Number/Procedure and (0040,1007) Filler Order Number/Procedure were previously defined in DICOM. They are now retired (see PS3.3-1998). + + + Attributes (0040,2006) and (0040,2007) were previously defined in DICOM. They are now retired (see PS3.3-1998). + + + +
+
+ Service Class User + The SCU uses the N-GET Service Element to request the SCP to get a Modality Performed Procedure Step Retrieve SOP Instance. The SCU shall specify in the N-GET request primitive the UID of the SOP Instance to be retrieved, which is a UID of a Modality Performed Procedure Step SOP Instance. The SCU shall be permitted to request that Attribute Values be returned for any Modality Performed Procedure Step Retrieve SOP Class Attribute specified in . Additionally values may be requested for optional Modality Performed Procedure Step IOD Attributes. + The SCU shall specify the list of Modality Performed Procedure Step Retrieve SOP Class Attributes for which values are to be returned. The encoding rules for Modality Performed Procedure Step Attributes are specified in the N-GET request primitive specification in . + In an N-GET operation, the values of Attributes that are defined within a Sequence of Items shall not be requested by an SCU. + The SCU shall be capable of receiving all requested Attribute Values provided by the SCP in response to the N-GET indication primitive. The SCU may request Attribute Values for optional Attributes that are not maintained by the SCP. In such a case, the SCU shall function properly regardless of whether the SCP returns values for those Attributes or not. This Service Class Specification places no requirements on what the SCU shall do as a result of receiving this information. + + In order to accurately interpret the character set used for the Attribute Values returned, it is recommended that the Attribute Value for the Specific Character Set (0008,0005) be requested in the N-GET request primitive. + +
+
+ Service Class Provider + The N-GET operation allows the SCU to request from the SCP selected Attribute values for a specific Modality Performed Procedure Step SOP Instance via a Modality Performed Procedure Step Retrieve SOP Instance. This operation shall be invoked through the use of the DIMSE N-GET Service used in conjunction with the appropriate Modality Performed Procedure Step Retrieve SOP Instance that equals the Modality Performed Procedure SOP Instance. The SCP shall retrieve the selected Attribute values from the indicated Modality Performed Procedure Step SOP Instance. + The SCP shall return, via the N-GET response primitive, the N-GET Response Status Code applicable to the associated request. A Failure Code shall indicate that the SCP has not retrieved the SOP Instance. Contingent on the N-GET Response Status, the SCP shall return, via the N-GET response primitive, Attribute Values for all requested Attributes maintained by the SCP. +
+
+ Status Codes + The status values that are specific for this SOP Class and DIMSE Service are defined in . See for additional response status codes. + + + + + + + + + + + + + + + + +
Response Status
+ + Service Status + + + + Further Meaning + + + + Response Status Code + +
+ Warning + + Requested optional Attributes are not supported + + 0001 +
+
+
+
+
+ Modality Performed Procedure Step Retrieve SOP Class UID + The Modality Performed Procedure Step Retrieve SOP Class shall be uniquely identified by the Modality Performed Procedure Step Retrieve SOP Class UID that shall have the value "1.2.840.10008.3.1.2.3.4". +
+
+ Conformance Requirements + Implementations providing conformance to the Modality Performed Procedure Step Retrieve SOP Class shall be conformant as described in the following sections and shall include within their Conformance Statement information as described below. + An implementation may conform to this SOP Class as an SCU or as an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that is conformant to this SOP Class as an SCU shall meet conformance requirements for the operations that it invokes. +
+ Operations + Any Attributes for which Attribute Values may be requested (using the N-GET Service) by the SCU shall be enumerated in the SCU Operations Statement. The SCU Operations Statement shall be formatted as defined in . +
+
+
+ SCP Conformance + An implementation that is conformant to this SOP Class as an SCP shall meet conformance requirements for the operations that it performs. +
+ Operations + Any Attributes for which Attribute Values may be requested (using the N-GET Service) by the SCU shall be enumerated in the SCP Operations Statement. The SCP Operations Statement shall be formatted as defined in . +
+
+
+
+
+ Modality Performed Procedure Step Notification SOP Class + The Modality Performed Procedure Step Notification SOP Class is intended for those Application Entities requiring notifications of Modality Performed Procedure Step's changes in state. + An Application Entity may choose to take some actions based upon a notification or request for information but is in no way required to do so. + + + + For example, in one configuration, an IS could be responsible for maintaining data related to performed procedure steps. A PACS reviewing workstation may need to display the images for any study viewed. In order for the PACS to link the images to the study, a PACS may receive a notification whenever a procedure step has been performed. In such a configuration the IS is the SCP and the PACS is the SCU. When the PACS receives this notification, it may link the images and the performed procedure step to the study within its internal database or may choose to take no action. + + + The terms IS and PACS used in the previous example are provided for clarification purposes only. This document does not define nor constrain the purpose or role of any IS, PACS or acquisition Application Entity conforming to this Service Class Specification. + + + +
+ DIMSE Service Group + + shows the DIMSE-N Services applicable to the Modality Performed Procedure Step IOD under the Modality Performed Procedure Step Notification SOP Class. + The DIMSE-N Services and Protocol are specified in . + + + + + + + + + + + + + + +
DIMSE-N Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-EVENT-REPORT + + M/M +
+
+
+ Notifications + The Application Entity that claims conformance as an SCU to this SOP Class shall be permitted to receive the following notification. The Application Entity that claims conformance as an SCP to this SOP Class shall be capable of providing the notifications defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Performed Procedure Step Notification Event Information
+ + Event Type Name + + + + Event Type ID + + + + Attribute Name + + + + Tag + + + + Req. Type SCU/SCP + +
+ Performed Procedure Step In Progress + + 1 + + + +
+ Performed Procedure Step Completed + + 2 + + + +
+ Performed Procedure Step Discontinued + + 3 + + + +
+ Performed Procedure Step Updated + + 4 + + + + An Update event shall not be used to notify changes in Performed Procedure Step Status (0040,0252). +
+ Performed Procedure Step Deleted + + 5 + + + +
+ + The Notification Event Information contains no Attributes, beyond those defined in . An SCU receiving a Notification and requiring further information may also be an SCU of the Modality Performed Procedure Step Retrieval SOP Class and may use the Affected SOP Instance UID (0000,1000) to perform an N-GET of the Modality Performed Procedure Step SOP Instance. + +
+ Receive Modality Performed Procedure Step Event Notification + This notification allows an SCU to receive from the SCP an unsolicited notification of a change in a Modality Performed Procedure Step SOP Instance. These notifications shall be invoked by the SCP through the use of the DIMSE N-EVENT-REPORT Service used in conjunction with the related Modality Performed Procedure Step SOP Instance. + The SCU shall return, via the N-EVENT-REPORT response primitive, the N-EVENT-REPORT Response Status Code applicable to the associated request. The SCU shall accept all Attributes included in any notification. This Service Class Specification places no requirements on what the SCU shall do as a result of receiving this information. + The same SOP Instance UID is shared by all three Modality Performed Procedure Step SOP Classes. This means that the SOP Instance created and set using the services of the Modality Performed Procedure Step SOP Class can be retrieved using its SOP Instance UID within the service of the Modality Performed Procedure Step Retrieve SOP Class. Changes in its state can be notified by using its SOP Instance UID within the request primitive of the Modality Performed Procedure Step Notification SOP Class. + The Modality Performed Procedure Step Notification SOP Instance UID shall not be used to identify a SOP Instance of the Study Component Service Class. +
+
+ Provide Modality Performed Procedure Step Event Notification + These notifications allow an SCU to receive from the SCP an unsolicited notification of a change in the state of a real-world performed procedure step. This notification shall be invoked by the SCP through the use of the DIMSE N-EVENT-REPORT Service used in conjunction with the related Modality Performed Procedure Step SOP Instance. + The SCP shall specify in the N-EVENT-REPORT request primitive the UID of the Modality Performed Procedure Step SOP Instance with which the event is associated and the Event Type ID. The Affected SOP Class UID specified in the DIMSE N-EVENT-REPORT request primitive shall be the UID of the Modality Performed Procedure Step Notification SOP Class. + + The encoding of Notification Event Information is defined in . + +
+
+ Status Codes + There are no specific status codes. See for response status codes. +
+
+
+ Modality Performed Procedure Step Notification SOP Class UID + The Modality Performed Procedure Step Notification SOP Class shall be uniquely identified by the Modality Performed Procedure Step Notification SOP Class UID that shall have the value "1.2.840.10008.3.1.2.3.5". +
+
+ Conformance Requirements + Implementations providing Standard SOP Class Conformance to the Modality Performed Procedure Step Notification SOP Class shall be conformant as described in the following sections and shall include within their Conformance Statement information as described in the following sections. + An implementation may conform to this SOP Class as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that is conformant to this SOP Class as an SCU shall meet conformance requirements for the: + + + notifications that it receives + + +
+ Notifications + All standard event types for which notifications may be requested by the SCU shall be enumerated in the SCU Notifications Statement. The SCU Notifications Statement shall include an enumerated list of the event types supported: + + + Performed Procedure Step In Progress + + + Performed Procedure Step Completed + + + Performed Procedure Step Discontinued + + + Performed Procedure Step Updated + + + Performed Procedure Step Deleted + + +
+
+
+ SCP Conformance + An implementation that is conformant to this SOP Class as an SCP shall meet conformance requirements for: + + + notifications that it invokes + + +
+ Notifications + Any optional Attributes that may be included in Standard notifications to the SCU shall be enumerated in the SCP Notifications Statement. The SCP Notifications Statement shall be formatted as defined in . Following this statement shall be the list of event types and optional Attributes. +
+
+
+
+
+ General Purpose Scheduled Procedure Step SOP Class (Retired) + Retired. See PS3.4-2011. +
+
+ General Purpose Performed Procedure Step SOP Class(Retired) + Retired. See PS3.4-2011. +
+
+ + Results Management Service Class (Normative) + Retired. See PS3.4-2004. + + + Print Management Service Class (Normative) +
+ Scope + The Print Management Service Class defines an application-level class-of-service that facilitates the printing of images and image related data on a hard copy medium. + + The DICOM Print Management Service Class covers the general cases of printing medical images in standardized layouts. An application can obtain more flexible layout, annotation, and formatting either by direct manipulation of the pixel matrices used in DICOM Print Management, or by utilizing page descriptions written in a page description language (such as Postscript or PDF) that are communicated to the printing system using commonly available protocols. These other page descriptions languages are not communicated using DICOM protocols and their use is outside the scope of the DICOM Standard. + +
+
+ Print Management Model +
+ Print Management Data Flow Model +
+ Global Data Flow Model + The Print Management Data Flow Model () consists of three main processes: + + + Film Session Management process + + + Print process + + + + The Standard uses the word film as a general name for different types of hard copy media (e.g., photographic film, paper). + + +
+ Print Management Data Flow Model + + + + + + +
+
+ The Film Session Management process is responsible for acquiring all the information that is required to print the film session. The film session is the atomic work package of the Print Management Application and contains one or more films related in a user defined way (e.g., belonging to the same exam, patient) that are originated from one host (e.g., workstation, diagnostic modality) and that are printed on one hard copy printer. + Each film consists of one or more images and zero or more film related annotations. An annotation consists of one or more lines of text. + Each image consists of pixel data and zero or more overlay planes. The user controls the look of the film by assigning values to print parameters. + Print parameters are defined at film session, film, image and annotation levels. The parameter level determines the scope of operation of the print parameters (e.g., print parameters of the image level are valid for the corresponding image). + The inputs of the Film Session Management process are: + + + set of images and image related data + + + presentation data that describes the visual look of the films + + + The output of the Film Session Management process is the Print Job, which contains all the information to print the film session. + The Print process prints a set of films, based on the information in the Print Job. The Print process is implementation specific and its management is beyond the scope of the DICOM standard. +
+
+ Grayscale Transformations + The Print Management Service Class supports two grayscale transformations and spatial transformations that converts an original image into a printed image. + The sequence of spatial transformations (e.g., magnification and merging of annotation with images) and their relationships with the grayscale transformations are implementation specific and fall beyond the scope of the DICOM Standard. + The sequence of grayscale transformations is important for achieving consistent image quality because of the non-orthogonal nature of the different transformations. describes the sequence of grayscale transformations. + + This section previously described Modality LUT and VOI LUT transformations in more detail. Since Referenced Print SOP Classes have been retired, these descriptions no longer apply to the Print Management Service Class. See PS 3.4-1998. + + +
+ Print Management Data Flow Model + + + + + + +
+
+
+ Modality and User Specific Transformations + Examples of these transformations are Modality LUT, Mask Subtraction, and VOI LUT. + The Modality LUT transforms manufacturer dependent pixel values into pixel values that are meaningful for the modality and are manufacturer independent. + The VOI LUT transforms the modality pixel values into pixel values that are meaningful for the user or the application. For example it selects of a range of pixel values to be optimized for display, such as soft tissue or bone windows in a CT image. +
+
+ Polarity + Polarity specifies whether minimum input pixel values shall be displayed as black or white. If Polarity (2020,0020) is NORMAL then the pixels will be displayed as specified by Photometric Interpretation; if Polarity is REVERSE then the pixels will be displayed with the opposite polarity as specified by Photometric Interpretation. + Polarity (2020,0020) is an Attribute of the Image Box IOD. +
+
+ Presentation LUT + The Presentation LUT transforms the polarity pixel values into Presentation Values (P-Values), which are meaningful for display of the images. P-Values are approximately related to human perceptual response. They are intended to facilitate consistent display with common input for both hardcopy and softcopy display devices and be independent of the specific class or characteristics of the display device. It is used to realize image display tailored for specific modalities, applications, and user preferences + In the Print Management Service Class, the Presentation LUT is part of the Presentation LUT IOD. + Hardcopy devices convert P-Values into optical density for printing. This conversion depends on desired image D-max and D-min. It also depends on expected viewing conditions such as lightbox intensity for transparency films. The conversion to printed density is specified in the Presentation LUT SOP Class. + If the modality desires to natively specify P-Values as its output, it can negotiate for support of the Presentation LUT, but specify a LUT that is an identity function. The identity function informs the display device that no further translation is necessary. + + Performing this translation in the printer prevents potential loss of precision (detail) that would occur if this translation were to be performed on many of the existing 8-bit modalities. + +
+
+
+
+ Print Management Service Class Structure + The Print Management Service Class Structure is shown in . + The Print Management SCU and Print Management SCP are peer DICOM Print Management Application Entities. The Application Entity of the Print Management SCP corresponds with one or more hard copy printers. If the SCP Application Entity corresponds with multiple printers then the SCP Application Entity selects for each Print Job the printer where the Print Job will be printed. + +
+ Print Management Service Class Structure + + + + + + +
+
+ The Print Management SCU and Print Management SCP establish an Association by using the Association Services of the OSI Upper Layer Service. During Association establishment, the DICOM Print Management Application Entities negotiate the supported SOP Classes. The negotiation procedure is defined in . + + shows alternative configurations for printing images and image related data from one host to multiple printers. + + + Configuration 1: one SCU Application Entity corresponds with the host and one SCP Application Entity corresponds with multiple printers. The SCU has no control over the print parameters of each printer and over the print destination of the Print Job. + + + Configuration 2: one SCU Application Entity corresponds with the host and one Application Entity SCP corresponds with each printer. The SCU has explicit control over the print parameters of each printer and over the print destination of the Print Job. Each SCP Application Entity has one Association with the SCU Application Entity and is identified by its Application Entity title. + + + +
+ Configurations for Printing On Multiple Printers + + + + + + +
+
+
+
+ Print Management SOP Classes + The Print Management SCU controls the Print Process by manipulating the Print Management SOP Classes by means of the DIMSE Services. The Print Management SOP Classes are managed by the Print Management SCP. + The Print Management SOP Classes are classified as follows: + + + Content related SOP Classes: these SOP Classes are an abstraction of the contents of a film (e.g., pixel data, text string). The content related SOP Classes correspond with the Image related SOP Classes, which are described in of this Part. + + + Presentation related SOP Classes: these SOP Classes are an abstraction of the presentation of a film (e.g., layout information) and are defined by Normalized IODs and Normalized DIMSE-N Services. The presentation related SOP Classes are defined in of this Part. + + + Printer related SOP Classes: these SOP Classes are an abstraction of the printer configuration and status and are defined by Normalized IODs. The Printer SOP Class is defined in of this Part. + + +
+
+ Usage Specifications + The building blocks of SOP Classes are Modules and DIMSE Services. The Modules contain related Attributes, which are Mandatory(M) or Optional (U). The usage may be different for the SCU and SCP. The usage is specified as a pair of letters: the former indicating the SCU usage, the latter indicating the SCP usage. + DIMSE Services may be Mandatory (M) or Optional (U) as specified in Section 5.4 of this Part. + The meaning and behavior of the usage specification for Attributes for the Print Management Service Class are: + + + M/M + + The SCU shall provide a value for the Attribute. If the SCU does not supply a value, the SCP shall return a Failure status ("Missing Attribute," code 0120H). The SCP shall support at least one value of the Attribute. If the SCP does not support the value specified by the SCU, it shall return a Failure status ("Invalid Attribute Value," code 0106H). + + + + -/M + + The SCU's usage of the Attribute is undefined. The SCP shall support at least one value of the Attribute. + + + + U/M + + The SCU may provide a value for the Attribute. If the SCP does not support the value specified by the SCU, it shall return either a Failure status ("Invalid Attribute Value", code 0106H) or return a Warning status ("Attribute Value Out of Range", code 0116H). In the case of Warning status, the SCP will apply the default value as defined in the SCP Conformance Statement. + + + + U/U + + The SCU may provide a value for the Attribute. If the SCP does not support the value specified by the SCU, but does support the Attribute, it shall return either a Failure status ("Invalid Attribute Value", code 0106H) or a Warning status ("Attribute Value out of Range", code 0116H.). In the case of Warning status, the SCP will apply the default value as defined in the SCP Conformance Statement. + + + + If the SCP does not support the Attribute specified by the SCU, it shall return either a Failure status ("No Such Attribute", code 0105H) or return a Warning status ("Attribute List Error", code 0107H.)). In the case of Warning status, the behavior of the SCP is defined in the SCP Conformance Statement. + If the usage type designation is modified by a "C" (e.g., MC/M) the specification stated above shall be modified to include the requirement that the Attribute shall be supported if the specified condition is met. +
+
+ Status Code Categories + For every operation requested on a SOP class of the print management service class, a status code will be returned. These status codes are grouped into success, warning or failure categories. + + These status codes categories are defined in : + + + Success - indicates that the SCP performed the requested operation as requested. + + + Warning - indicates that the SCP has received the request and will process it. However, immediate processing of the request, or processing in the way specified by the SCU, may not be possible. The SCP expects to be able to complete the request without further action by the SCU across the DICOM interface. The exact behavior of the SCP is described in the Conformance Statement. + + + Failure - indicates that the SCP is unable to perform the request. The request will not be processed unless it is repeated by the SCU at a later time. The exact behavior of the SCP is described in the Conformance Statement. + + + +
+
+
+ Print Management Conformance +
+ Scope + Print Management conformance is defined in terms of supported Meta SOP Classes, which correspond with the mandatory functionality, and of supported optional SOP Classes, which correspond with additional functionality. + A Meta SOP Class corresponds with a pre-defined group of SOP Classes. The following Print Management Meta SOP Classes are defined: + + + Basic Grayscale Print Management Meta SOP Class + + + Basic Color Print Management Meta SOP Class + + + All SCUs and SCPs of the Print Management Service Class shall support at least one of the Basic Print Management Meta SOP Classes. + In addition the other Meta SOP Classes or optional SOP Classes may be supported. + The Meta SOP Class level negotiation is used to define a minimum set of print functions; the SOP Class level negotiation is used to define additional functions. + If multiple Meta SOP Classes and one or more optional SOP Classes are negotiated, the SCP shall support all the optional SOP Classes in conjunction with all the Meta SOP Classes. + At association setup, the negotiation process between the Print Management SCU and SCP shall occur for + + + one or more of the Meta SOP Classes and zero or more of the optional SOP Classes specified in ; or + + + one or more of the Printer, Print Job, and Printer Configuration Retrieval SOP Classes. + + + + It is possible for an SCP to support Associations for printing and to also support additional Associations for the sole purpose of exchanging status information about the printer. + +
+
+ Print Management Meta SOP Classes +
+ Description + The Basic Print Management Meta SOP Classes correspond with the minimum functionality that an implementation of the Print Management Service Class shall support. The Basic Print Management Meta SOP Classes support the following mandatory features: + + + preformatted grayscale images or preformatted color images; preformatted images are images where annotation, graphics, overlays are burned in + + + pre-defined film layouts (image display formats) + + + basic presentation parameters on film session, film box and image box level + + + basic device management + + + The optional SOP Classes described in may be used with the Basic Print Management Meta SOP Classes. + The following features are optional for SCUs and SCPs: + + + Film box annotation + + + Presentation LUT + + +
+
+ Meta SOP Class Definitions +
+ Basic Grayscale Print Management Meta SOP Class + The Meta SOP Class is defined by the following set of supported SOP Classes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Classes of Basic Grayscale Print Management Meta SOP Class
+ + SOP Class Name + + + + Reference + + + + Usage SCU/SCP + +
+ Basic Film Session SOP Class + + + + + + M/M +
+ Basic Film Box SOP Class + + + + + + M/M +
+ Basic Grayscale Image Box SOP Class + + + + + + M/M +
+ Printer SOP Class + + + + + + M/M +
+ + The image pixel data are part of the Basic Grayscale Image Box SOP Class + + The meaning of the Usage SCU/SCP is described in . + The Basic Grayscale Print Management Meta SOP Class UID has the value "1.2.840.10008.5.1.1.9". +
+
+ Basic Color Print Management Meta SOP Class + The Meta SOP Class is defined by the following set of supported SOP Classes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Classes of Basic Color Print Management Meta SOP Class
+ + SOP Class Name + + + + Reference + + + + Usage SCU/SCP + +
+ Basic Film Session SOP Class + + + + + + M/M +
+ Basic Film Box SOP Class + + + + + + M/M +
+ Basic Color Image Box SOP Class + + + + + + M/M +
+ Printer SOP Class + + + + + + M/M +
+ + The image pixel data are part of the Basic Color Image Box SOP Class + + The meaning of the Usage SCU/SCP is described in . + The Basic Color Print Management Meta SOP Class UID has the value "1.2.840.10008.5.1.1.18". +
+
+ Referenced Grayscale Print Management Meta SOP Class (Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-1998. +
+
+ Referenced Color Print Management Meta SOP Class (Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-1998. +
+
+ Pull Stored Print Management Meta SOP Class(Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-2004. +
+
+
+
+ Optional SOP Classes +
+ Description + The optional SOP Classes address functionality beyond that of the Print Management Meta SOP Classes. One or more optional SOP Classes may be used in addition to the Print Management Meta SOP Classes. + The following functionality is supported by the optional SOP Classes: + + + annotation (text associated with a sheet of film) + + + tracking the printing of the print session + + + retrieval of printer configuration information + + + Presentation LUTs + + + Use of these optional SOP Classes allows an SCU to provide information to be printed with or on an image without burning the information into the image pixels. If these optional SOP Classes are not supported by both the SCU and SCP, then only the information burnt in to the image pixels before they are sent to the SCP will be printed. If the optional SOP Classes are not supported, the SCU is responsible for burning all expected text or graphics into the image pixels. +
+
+ List of Optional SOP Classes + The following optional SOP Classes may be used in conjunction with the Basic Print Management Meta SOP Classes specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
List of Optional SOP Classes for Basic Print Management Meta SOP Classes
+ + SOP Class Name + + + + Reference + + + + Usage SCU/SCP + +
+ Basic Annotation Box SOP Class + + + + + + U/U +
+ Print Job SOP Class + + + + + + U/U +
+ Presentation LUT SOP Class + + + + + + U/U +
+ Printer Configuration Retrieval SOP Class + + + + + + U/U +
+ + Negotiation of the Presentation LUT SOP Class does not imply any behavior in the SCP. Behavior is explicit when the Presentation LUT SOP Class is created and referenced at either the Film Session, Film Box, or Image Box levels. + +
+
+
+ Conformance Statement + The implementation Conformance Statement of these SOP Classes shall follow . + The SCU Conformance Statement shall specify the following items: + + + maximum number of supported Associations at the same time + + + list of supported SOP Classes and Meta SOP Classes + + + for each of the supported SOP and Meta SOP Classes: + + + list of supported optional SOP Class Attributes and DIMSE Service Elements + + + for each supported Attribute (mandatory and optional Attribute), the valid range of values + + + The SCP Conformance Statement shall specify the following items: + + + maximum number of supported Associations at the same time + + + list of supported SOP Classes and Meta SOP Classes + + + minimum and maximum number of printable pixel matrix per supported film size + + + for each of the supported SOP Classes: + + + list of supported optional SOP Class Attributes and DIMSE Service Elements + + + for each supported Attribute (mandatory and optional Attribute): + + + valid range of values + + + default value if no value is supplied by the SCU + + + status code (Failure or Warning) if SCU supplies a value that is out of range + + + for each supported DIMSE Service, the SCP behavior for all specific status codes + + + description of each supported custom Image Display Format (2010,0010) e.g., position and dimensions of each composing image box, numbering scheme of the image positions + + + description of each supported Annotation Display Format ID (2010,0030) e.g., position and dimensions of annotation box, font, number of characters + + + description of each supported configuration table (e.g., identification, content) + + + if the SCP supports N-ACTION for the Film Session SOP Class then the SCP shall specify the maximum number of collated films + + + in the case of grayscale printers that print color images, the behavior of printing color images + + + if cropping of images is supported, the algorithm for removing rows and columns from the image + + +
+
+
+ Print Management SOP Class Definitions +
+ Basic Film Session SOP Class +
+ IOD Description + The Basic Film Session IOD describes the presentation parameters that are common for all the films of a film session (e.g., number of films, film destination) + The Basic Film Session SOP Instance refers to one or more Basic Film Box SOP Instances. +
+
+ DIMSE Service Group + The DIMSE Services applicable to the IOD are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-SET + + U/M +
+ N-DELETE + + U/M +
+ N-ACTION + + U/U +
+ The meaning of the Usage SCU/SCP is described in . + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-CREATE + The N-CREATE is used to create an instance of the Basic Film Session SOP Class. +
+ Attributes + The Attribute list of the N-CREATE is defined as shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-CREATE Attribute List
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Specific Character Set + + (0008,0005) + + U/U +
+ Number of Copies + + (2000,0010) + + U/M +
+ Print Priority + + (2000,0020) + + U/M +
+ Medium Type + + (2000,0030) + + U/M +
+ Film Destination + + (2000,0040) + + U/M +
+ Film Session Label + + (2000,0050) + + U/U +
+ Memory Allocation + + (2000,0060) + + U/U +
+ Owner ID + + (2100,0160) + + U/U +
+ + + + The memory allocation Attribute allows the SCU to reserve sufficient memory to store the "working" film session hierarchy as well the "copied" film session hierarchy in the Print Job in order to prevent deadlock situations. + + + Owner ID (2100,0160) is a user option for the Basic Film Session. + + + + The meaning of the Usage SCU/SCP is described in . + Within the film session, the allocated memory is consumed as SOP Instances are created and is freed for reuse as SOP Instances are deleted. All the allocated memory shall be released following termination of the Association or deletion of the Film Session SOP Instance. +
+
+ Status + The status values that are specific for this SOP Class are defined as follows. + + + + + + + + + + + + + + + + + + + + + +
Status Values for Basic Film Session SOP Class
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Film session successfully created + + 0000 +
+ Warning + + Memory allocation not supported + + B600 +
+ + The status code "0106H" (Invalid Attribute Value) indicates that the requested memory allocation can not be provided; the status code "0213H" (Resource limitation) indicates that the requested allocation can temporarily not be provided. + +
+
+ Behavior + The SCU uses the N-CREATE to request the SCP to create a Basic Film Session SOP Instance. The SCU shall initialize Attributes of the SOP Class as specified in . + The SCP shall create the SOP Instance and shall initialize Attributes of the SOP Class as specified in . + The SCP shall return the status code of the requested SOP Instance creation. The meaning of success, warning, and failure status codes is defined in . + The Basic Film Session SOP Instances shall be created before the Film Box SOP Instances are created. + At any time the SCU/SCP shall only support one Basic Film Session SOP Instance on an Association. + + Multiple film sessions may be handled by establishing multiple Associations. + + Terminating the Association will effectively perform an N-DELETE on an opened film session. See Note in . +
+
+
+ N-SET + The N-SET may be used to update an instance of the Basic Film Session SOP Class. +
+ Attributes + All Attributes and usage in apply to N-SET. +
+
+ Status + The status values that are specific for this SOP Class are defined in . +
+
+ Behavior + The SCU uses the N-SET to request the SCP to update a Basic Film Session SOP Instance. The SCU shall specify the SOP Instance UID to be updated and shall specify the list of Attributes for which the Attribute Values are to be set. + The SCP shall set new values for the specified Attributes of the specified SOP Instance. + The SCP shall return the status code of the requested SOP Instance update. The meaning of success, warning, and failure status codes is defined in + +
+
+
+ N-DELETE + The N-DELETE is used to delete the complete Basic Film Session SOP Instance hierarchy. As a result, all references to Image SOP Instances within the film session are deleted. + The Basic Film Session SOP Instance hierarchy consists of one Basic Film Session SOP Instance, one or more Basic Film Box SOP Instances, one or more Image Box SOP Instances, zero or more Basic Annotation Box SOP Instances, zero or more Presentation LUT SOP Instances, and zero or more Basic Print Image Overlay Box SOP instances. + + The Basic Film Session SOP Instance hierarchy can be visualized as a reversed tree with the Basic Film Session SOP Instance as the root and the Image Box SOP Instances as the leaves. + +
+ Status + There are no specific status codes. +
+
+ Behavior + The SCU uses the N-DELETE to request the SCP to delete the Basic Film Session SOP Instance hierarchy. The SCU shall specify in the N-DELETE request primitive of the SOP Instance UID of the Basic Film Session (root). + The SCP shall delete the specified SOP Instance hierarchy. + The SCP shall not delete SOP Instances in the hierarchy as long as there are outstanding references to these SOP Instances + + It is beyond the scope of the Standard to specify when the SCP actually deletes SOP Instances with outstanding references. + + The SCP shall return the status code of the requested SOP Instance deletion. The meaning of success, warning, and failure status codes is defined in . +
+
+
+ N-ACTION + The N-ACTION is used to print the film session; i.e., to print all the films that belong to the film session. + If multiple copies of the film session have been requested, the SCP shall collate the copies. This means that if two copies of four films has been specified, the printed sequence is 12341234. +
+ Attributes + The arguments of the N-ACTION are defined in . + The Action Reply argument is encoded as a DICOM Data Set. The Data Set only contains the Attribute Referenced Print Job Sequence (2100,0500), which includes the Referenced SOP Class UID (0008,1150) and the Referenced SOP Instance UID (0008,1155). + If the SCP supports the Print Job SOP Class, the Action Reply argument is contained in the N-ACTION response. Otherwise, the Action Reply is not contained in the N-ACTION response. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-ACTION Arguments
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Print + + 1 + + Referenced Print Job Sequence + + (2100,0500) + + -/MC + Required if Print Job SOP is supported +
+ + + >Referenced SOP Class UID + + (0008,1150) + + -/MC + Required if Referenced Print Job Sequence (2100,0500) is present +
+ + + >Referenced SOP Instance UID + + (0008,1155) + + -/MC + Required if Referenced Print Job Sequence (2100,0500) is present +
+
+
+ Status + The status values that are specific for this SOP Class are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Class Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Film belonging to the film session are accepted for printing; if supported, the Print Job SOP Instance is created + + 0000 +
+ Warning + + Film session printing (collation) is not supported + + B601 +
+ + Film Session SOP Instance hierarchy does not contain Image Box SOP Instances (empty page) + + B602 +
+ + Image size is larger than image box size, the image has been demagnified. + + B604 +
+ + Image size is larger than the Image Box size. The Image has been cropped to fit. + + B609 +
+ + Image size or Combined Print Image size is larger than the Image Box size. Image or Combined Print Image has been decimated to fit. + + B60A +
+ Failure + + Film Session SOP Instance hierarchy does not contain Film Box SOP Instances + + C600 +
+ + Unable to create Print Job SOP Instance; print queue is full + + C601 +
+ + Image size is larger than image box size + + C603 +
+ + Combined Print Image size is larger than the Image Box size + + C613 +
+ + Previous versions of the DICOM Standard defined the status code of C604. This code was specified for the case of an image position collision. Since image position collision is not a possible state, the code has been retired. + +
+
+ Behavior + The SCU uses the N-ACTION to request the SCP to print all the films belonging to the identified film session. + The SCP shall make a copy of the "working" Basic Film Session SOP Instance hierarchy, which contains all the information to control the Print Process. Hence the SCU may further update the "working" SOP Instance hierarchy without affecting the result of previous print requests. The execution of the Print Process is monitored by the Print Job SOP Instance (if supported by the SCP) and the Printer SOP Class. + If the SCP supports the Print Job SOP Class then the SCP shall create a Print Job SOP Instance, which contains the copy of the "working" Basic Film Session SOP Instance hierarchy and shall return the Print Job SOP Class/Instance UID pair in the Attribute Referenced Print Job Sequence of the Action Reply argument. + + If the SCP supports the Print Job SOP Class, it creates a single Print Job for all the films of the film session. + + The SCP shall return the status code of the requested operation. The meaning of success, warning, and failure status codes is defined in . + The N-ACTION shall be issued only if the Basic Film Session SOP Instance hierarchy contains at least one Film Box SOP Instance. +
+
+
+
+ SOP Class Definition and UID + The Basic Film Session SOP Class UID shall have the value "1.2.840.10008.5.1.1.1". +
+
+
+ Basic Film Box SOP Class +
+ IOD Description + The Basic Film Box IOD is an abstraction of the presentation of one film of the film session. The Basic Film Box IOD describes the presentation parameters that are common for all images on a given sheet of film. + The Basic Film Box SOP Instance refers to one or more Image Box SOP Instances, zero or more film related Annotation Box SOP Instances, and zero or one Presentation LUT SOP Instance. +
+
+ DIMSE Service Group + + shows DIMSE Services applicable to the IOD. + + + + + + + + + + + + + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-ACTION + + M/M +
+ N-DELETE + + U/M +
+ N-SET + + U/U +
+ The meaning of the Usage SCU/SCP is described in . + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-CREATE + The N-CREATE is used to create an instance of the Basic Film Box SOP Class. +
+ Attributes + The Attribute list of the N-CREATE is shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-CREATE Attribute List
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Image Display Format + + (2010,0010) + + M/M +
+ Referenced Film Session Sequence + + (2010,0500) + + M/M +
+ >Referenced SOP Class UID + + (0008,1150) + + M/M +
+ >Referenced SOP Instance UID + + (0008,1155) + + M/M +
+ Referenced Image Box Sequence + + (2010,0510) + + -/M +
+ >Referenced SOP Class UID + + (0008,1150) + + -/M +
+ >Referenced SOP Instance UID + + (0008,1155) + + -/M +
+ Referenced Basic Annotation Box Sequence + + (2010,0520) + + -/MC + (Required if optional Annotation SOP was negotiated) +
+ >Referenced SOP Class UID + + (0008,1150) + + -/MC + (Required if sequence is present) +
+ >Referenced SOP Instance UID + + (0008,1155) + + -/MC + (Required if sequence is present) +
+ Film Orientation + + (2010,0040) + + U/M +
+ Film Size ID + + (2010,0050) + + U/M +
+ Magnification Type + + (2010,0060) + + U/M +
+ Max Density + + (2010,0130) + + U/M +
+ Configuration Information + + (2010,0150) + + U/M +
+ Referenced Presentation LUT Sequence + + (2050,0500) + + U/MC + (Required if Presentation LUT is supported) +
+ >Referenced SOP Class UID + + (0008,1150) + + U/MC + (Required if sequence is present) +
+ >Referenced SOP Instance UID + + (0008,1155) + + U/MC + (Required if sequence is present) +
+ Annotation Display Format ID + + (2010,0030) + + U/U +
+ Smoothing Type + + (2010,0080) + + U/U +
+ Border Density + + (2010,0100) + + U/U +
+ Empty Image Density + + (2010,0110) + + U/U +
+ Min Density + + (2010,0120) + + U/U +
+ Trim + + (2010,0140) + + U/U +
+ Illumination + + (2010,015E) + + U/MC + (Required if Presentation LUT is supported) +
+ Reflected Ambient Light + + (2010,0160) + + U/MC + (Required if Presentation LUT is supported) +
+ Requested Resolution ID + + (2020,0050) + + U/U +
+ ICC Profile + + (0028,2000) + + U/U +
+ The meaning of the Usage SCU/SCP is described in . + If the Illumination (2010,015E) and Reflected Ambient Light (2010,0160) values, respectively termed L0 and La, are not created, the following default values are recommended for grayscale printing: + + + For transmissive film: L0 = 2000 cd/m2. La = 10 cd/m2. + + + For reflective media: L0 = 150 cd/m2. + + + The ICC Profile (0028,2000) Attribute shall only be used to describe the color space of images for color printing, i.e., in conjunction with the Basic Color Image Box SOP Class. It shall not be used with the Basic Grayscale Image Box SOP Class. +
+
+ Status + The status values that are specific for this SOP Class are defined as follows: + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values for Basic Film Box SOP Class
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Film Box successfully created + + 0000 +
+ Warning + + Requested Min Density or Max Density outside of printer's operating range. The printer will use its respective minimum or maximum density value instead. + + B605 +
+ Failure + + There is an existing Film Box that has not been printed and N-ACTION at the Film Session level is not supported. A new Film Box will not be created when a previous Film Box has not been printed. + + C616 +
+
+
+ Behavior + The SCU uses the N-CREATE to request the SCP to create a Basic Film Box SOP Instance. The SCU shall initialize Attributes of the SOP Class as specified in . + The SCP shall create the SOP Instance and shall initialize Attributes of the SOP Class as specified in . + + If there exists a Film Box SOP Instance that has not been printed and the SCP does not support N-ACTION on the Film Session, then the SCP should fail the N-CREATE of the new SOP Instance. + + Upon the creation of the Basic Film Box SOP Instance, the SCP shall append the SOP Class/Instance UID pair of the created Basic Film Box SOP Instance to the Attribute Referenced Film Box Sequence (2000,0500) of the parent Basic Film Session SOP Instance to link the Basic Film Box SOP Instance to the Basic Film Session SOP Instance. + The SCP shall create Image Box SOP Instances of the appropriate Image Box SOP Class for each image box as defined by the Attribute Image Display Format (2010,0010). The SOP Class of the created Image Box SOP Instance depends on the Meta SOP Class context. For example the Grayscale Image Box SOP Class is related to the Basic Grayscale Print Management Meta SOP Class. The Meta SOP Class context is conveyed by the Presentation Context ID that corresponds with the Meta SOP Class and is defined at Association setup. + The SCP shall append the SOP Class/Instance UID pair of the created Image Box SOP Instance to the Referenced Image Box Sequence Attribute of the parent Basic Film Box SOP Instance to link each Image Box SOP Instance to the Basic Film Box SOP Instance. The SCP returns the list of Image Box SOP Class/Instance UID pairs in the Attribute Referenced Image Box Sequence (2010,0510) of the N-CREATE response message. + If supported, the SCP shall create Basic Annotation Box SOP Instances for each Annotation Box defined by the Attribute Annotation Display Format ID and shall append the SOP Class/Instance UID pair of the created Basic Annotation Box SOP Instance to the Referenced Annotation Box Sequence Attribute of the parent Basic Film Box SOP Instance to link each Basic Annotation Box SOP Instance to the Basic Film Box SOP Instance. The SCP returns the list of Basic Annotation Box SOP Class/Instance UID pairs in the Attribute Referenced Annotation Box Sequence of the N-CREATE response message. The Annotation Boxes shall support the same character sets as the Basic Film Box. + The character set supported by the Film Box shall be the same as the character set of the Basic Film Session. + The SCP shall return the status code of the requested SOP Instance creation. The meaning of success, warning, and failure status codes is defined in . +
+
+
+ N-SET + The N-SET may be used to update the last created instance of the Basic Film Box SOP Class. +
+ Attributes + The Attributes that may be updated are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-SET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Magnification Type + + (2010,0060) + + U/M +
+ Max Density + + (2010,0130) + + U/M +
+ Configuration Information + + (2010,0150) + + U/M +
+ Referenced Presentation LUT Sequence + + (2050,0500) + + U/MC + (Required if Presentation LUT is supported) +
+ >Referenced SOP Class UID + + (0008,1150) + + U/MC + (Required if sequence is present) +
+ >Referenced SOP Instance UID + + (0008,1155) + + U/MC + (Required if sequence is present) +
+ Smoothing Type + + (2010,0080) + + U/U +
+ Border Density + + (2010,0100) + + U/U +
+ Empty Image Density + + (2010,0110) + + U/U +
+ Min Density + + (2010,0120) + + U/U +
+ Trim + + (2010,0140) + + U/U +
+ Illumination + + (2010,015E) + + U/MC + (Required if Presentation LUT is supported) +
+ Reflected Ambient Light + + (2010,0160) + + U/MC + (Required if Presentation LUT is supported) +
+ ICC Profile + + (0028,2000) + + U/U +
+ The meaning of the Usage SCU/SCP is described in . +
+
+ Status + The status values that are specific for this SOP Class are defined in . +
+
+ Behavior + The SCU uses the N-SET to request the SCP to update a Basic Film Box SOP Instance. The SCU shall only specify the SOP Instance UID of the last created Basic Film Box SOP Instance in the N-SET request primitive, and shall specify the list of Attributes for which the Attribute Values are to be set. + The SCP shall set new values for the specified Attributes of the specified SOP Instance. + The SCP shall return the status code of the requested SOP Instance update. The meaning of success, warning, and failure status codes is defined in . +
+
+
+ N-DELETE + The N-DELETE is used to delete the last created Basic Film Box SOP Instance hierarchy. As a result all the information describing the last film is deleted. + The Basic Film Box SOP Instance hierarchy consists of one Basic Film Box SOP Instance, one or more Image Box SOP Instances, zero or more Basic Annotation Box SOP Instances, zero or more Presentation LUT SOP Instances, and zero or more Basic Print Image Overlay Box SOP instances. + + There is no provision in the DICOM Standard to delete previously created Film Box SOP Instances. + +
+ Behavior + The SCU uses the N-DELETE to request the SCP to delete the Basic Film Box SOP Instance hierarchy. The SCU shall specify in the N-DELETE request primitive the SOP Instance UID of the last created Basic Film Box (root). + The SCP shall delete the specified SOP Instance hierarchy and shall remove the UID of the deleted Basic Film Box SOP Instance from the list of SOP Instance UIDs of the Film Box UIDs Attribute of the parent Basic Film Session SOP Instance. + The SCP shall return the status code of the requested SOP Instance hierarchy deletion. The meaning of success, warning, and failure status codes is defined in . + The SCP shall not delete SOP Instances in the hierarchy as long as there are outstanding references to these SOP Instances + + It is beyond the scope of the Standard to specify when the SCP actually deletes the Image SOP Instances with outstanding references. + +
+
+
+ N-ACTION + The N-ACTION is used to print one or more copies of the last created instance of the Film Box. +
+ Attributes + The arguments of the N-ACTION are defined as shown in . + The Action Reply argument is encoded as a DICOM Data Set. The Data Set only contains the Attribute Referenced Print Job Sequence (2100,0500), which includes the Referenced SOP Class UID (0008,1150) and the Referenced SOP Instance UID (0008,1155). + If the SCP supports the Print Job SOP Class, the Action Reply argument is contained in the N-ACTION response. Otherwise, the Action Reply is not contained in the N-ACTION response. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-ACTION Arguments
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Print + + 1 + + Referenced Print Job Sequence + + (2100,0500) + + -/MC + Required if Print Job SOP is supported +
+ + + >Referenced SOP Class UID + + (0008,1150) + + -/MC + Required if Referenced Print Job Sequence (2100,0500) is present +
+ + + >Referenced SOP Instance UID + + (0008,1155) + + -/MC + Required if Referenced Print Job Sequence (2100,0500) is present +
+
+
+ Status + The status values that are specific for this SOP Class are defined as shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Film accepted for printing; if supported, the Print Job SOP Instance is created + + 0000 +
+ Warning + + Film Box SOP Instance hierarchy does not contain Image Box SOP Instances (empty page) + + B603 +
+ + Image size is larger than image box size, the image has been demagnified. + + B604 +
+ + Image size is larger than the Image Box size. The Image has been cropped to fit. + + B609 +
+ + Image size or Combined Print Image size is larger than the Image Box size. Image or Combined Print Image has been decimated to fit. + + B60A +
+ Failure + + Unable to create Print Job SOP Instance; print queue is full + + C602 +
+ + Image size is larger than image box size + + C603 +
+ + Combined Print Image size is larger than the Image Box size + + C613 +
+ + Previous versions of the DICOM Standard defined the status code of C604. This code was specified for the case of an image position collision. Since image position collision is not a possible state, the code has been retired. + +
+
+ Behavior + The SCU uses the N-ACTION to request the SCP to print one or more copies of a single film of the film session. The SCU shall only specify the SOP Instance UID of the last created Basic Film Box SOP Instance in the N-ACTION request primitive. + The SCP shall make a copy of the "working" Basic Film Session SOP Instance and the "working" Basic Film Box SOP Instance hierarchy, which contains all the information to control the Print Process. Hence the SCU may further update the "working" SOP Instances without affecting the result of previous print requests. The execution of the Print Process is monitored by the Print Job SOP Class (if supported by the SCP) and the Printer SOP Class. + If the SCP supports the Print Job SOP Class then the SCP shall create a Print Job SOP Instance, which contains the copy of the "working" Basic Film Session SOP Instance hierarchy and shall return the Print Job SOP Class/Instance UID pair in the Attribute Referenced Print Job Sequence of the Action Reply argument. + The SCP shall return the status code of the requested operation. The meaning of success, warning, and failure status codes is defined in . +
+
+
+
+ SOP Class Definition and UID + The Basic Film Box SOP Class UID shall have the value "1.2.840.10008.5.1.1.2". +
+
+
+ Image Box SOP Classes +
+ Basic Grayscale Image Box SOP Class +
+ IOD Description + The Basic Image Box IOD is an abstraction of the presentation of an image and image related data in the image area of a film. The Basic Image Box IOD describes the presentation parameters and image pixel data that apply to a single image of a sheet of film. + The Basic Grayscale Image Box SOP Instance is created by the SCP at the time the Basic Film Box SOP Instance is created, based on the value of the Basic Film Box Attribute Image Display Format (2010,0010). + The Basic Grayscale Image Box SOP Instance refers to zero or one Image Overlay Box SOP Instance and zero or one Presentation LUT SOP Instance. +
+
+ DIMSE Service Group + The DIMSE Services applicable to the IOD are shown below. + + + + + + + + + + + + + + +
DIMSE Services Applicable to Basic Grayscale Image Box
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-SET + + M/M +
+ The meaning of the Usage SCU/SCP is described in . + + There is no N-CREATE because Instances of the Basic Grayscale Image Box SOP Class are created by the SCP as a result of the N-CREATE of the Film Box SOP Instance. + + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-SET + The N-SET may be used to update an instance of the Basic Grayscale Image Box SOP Class. +
+ Attributes + The Attributes that may be updated are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-SET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Image Box Position + + (2020,0010) + + M/M +
+ Basic Grayscale Image Sequence + + (2020,0110) + + M/M +
+ >Samples Per Pixel + + (0028,0002) + + M/M +
+ >Photometric Interpretation + + (0028,0004) + + M/M +
+ >Rows + + (0028,0010) + + M/M +
+ >Columns + + (0028,0011) + + M/M +
+ >Pixel Aspect Ratio + + (0028,0034) + + MC/M + (Required if the aspect ration is not 1\1) +
+ >Bits Allocated + + (0028,0100) + + M/M +
+ >Bits Stored + + (0028,0101) + + M/M +
+ >High Bit + + (0028,0102) + + M/M +
+ >Pixel Representation + + (0028,0103) + + M/M +
+ >Pixel Data + + (7FE0,0010) + + M/M +
+ Polarity + + (2020,0020) + + U/M +
+ Magnification Type + + (2010,0060) + + U/U +
+ Smoothing Type + + (2010,0080) + + U/U +
+ Min Density + + (2010,0120) + + U/U +
+ Max Density + + (2010,0130) + + U/U +
+ Configuration Information + + (2010,0150) + + U/U +
+ Requested Image Size + + (2020,0030) + + U/U +
+ Requested Decimate/Crop Behavior + + (2020,0040) + + U/U +
+ Referenced Presentation LUT Sequence + + (2050,0500) + + U/U +
+ > Referenced SOP Class UID + + (0008,1150) + + U/U +
+ > Referenced SOP Instance UID + + (0008,1155) + + U/U +
+ The meaning of the Usage SCU/SCP is described in . + The values of Magnification Type (2010,0060) and Smoothing Type (2010,0080) of a particular image box override the values of Magnification Type and Smoothing Type of the film box. + Values for Referenced Presentation LUT Sequence override any Presentation LUT that may have been set at the Basic Film Box. Values for Min/Max Density override any Density values that may have been set at the Basic Film Box. +
+
+ Status + The status values that are specific for this SOP Class are defined as follows. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values for Basic Grayscale Image Box SOP Class
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Image successfully stored in Image Box + + 0000 +
+ Warning + + Image size larger than image box size, the image has been demagnified. + + B604 +
+ + Requested Min Density or Max Density outside of printer's operating range. The printer will use its respective minimum or maximum density value instead. + + B605 +
+ + Image size is larger than the Image Box size. The Image has been cropped to fit. + + B609 +
+ + Image size or Combined Print Image size is larger than the Image Box size. The Image or Combined Print Image has been decimated to fit. + + B60A +
+ Failure + + Image size is larger than image box size + + C603 +
+ + Insufficient memory in printer to store the image + + C605 +
+ + Combined Print Image size is larger than the Image Box size + + C613 +
+
+
+ Behavior + The SCU uses the N-SET to request the SCP to update a Basic Grayscale Image Box SOP Instance. The SCU shall only specify the SOP Instance UID of a Basic Grayscale Image Box belonging to the last created Film Box SOP Instance and shall specify the list of Attributes for which the Attribute Values are to be set. + To instruct the SCP to erase the image in the image position, the SCU shall set a zero length and no value in the Attribute Basic Grayscale Image Sequence (2020,0110). + The SCP shall set new values for the specified Attributes of the specified SOP Instance. + + The image in this N-SET supersedes any image previously set in the Image Box. + + The SCP shall return the status code of the requested SOP Instance update. The meaning of success, warning, and failure status codes is defined in . + If Requested Decimate/Crop Behavior (2020,0040) specifies DECIMATE, Magnification Type (2010,0060) specifies NONE, and the image is too large to fit the Image Box, the SCP shall fail the N-SET. +
+
+
+
+ SOP Class Definition and UID + The Basic Grayscale Image Box SOP Class UID shall have the value "1.2.840.10008.5.1.1.4". +
+
+
+ Basic Color Image Box SOP Class +
+ IOD Description + The Basic Image Box IOD is an abstraction of the presentation of an image and image related data in the image area of a film. The Basic Image Box IOD describes the presentation parameters and image pixel data that apply to a single image of a sheet of film. + The Basic Color Image Box SOP Instance is created by the SCP at the time the Basic Film Box SOP Instance is created, based on the value of the Basic Film Box Attribute Image Display Format (2010,0010). + The Basic Color Image Box SOP Instance refers to zero or one Image Overlay Box SOP Instance. +
+
+ DIMSE Service Group + The following DIMSE Services are applicable to the IOD. + + + + + + + + + + + + + + +
DIMSE Services Applicable to Basic Color Image Box
+ + DIMSE Service element + + + + Usage SCU/SCP + +
+ N-SET + + M/M +
+ The meaning of the Usage SCU/SCP is described in . + + There is no N-CREATE because Instances of the Basic Color Image Box SOP Class are created by the SCP as a result of the N-CREATE of the Film Box SOP Instance. + + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-SET + The N-SET may be used to update an instance of the Basic Color Image Box SOP Class. +
+ Attributes + The Attributes that may be updated are shown in . + The meaning of the Usage SCU/SCP is described in . + The values of Magnification Type (2010,0060) and Smoothing Type (2010,0080) of a particular image box override the values of Magnification Type and Smoothing Type of the film box. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-SET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Image Box Position + + (2020,0010) + + M/M +
+ Basic Color Image Sequence + + (2020,0111) + + M/M +
+ >Samples Per Pixel + + (0028,0002) + + M/M +
+ >Photometric Interpretation + + (0028,0004) + + M/M +
+ >Planar Configuration + + (0028,0006) + + M/M +
+ >Rows + + (0028,0010) + + M/M +
+ >Columns + + (0028,0011) + + M/M +
+ >Pixel Aspect Ratio + + (0028,0034) + + MC/M + (Required if the aspect ration is not 1\1) +
+ >Bits Allocated + + (0028,0100) + + M/M +
+ >Bits Stored + + (0028,0101) + + M/M +
+ >High Bit + + (0028,0102) + + M/M +
+ >Pixel Representation + + (0028,0103) + + M/M +
+ >Pixel Data + + (7FE0,0010) + + M/M +
+ Polarity + + (2020,0020) + + U/M +
+ Magnification Type + + (2010,0060) + + U/U +
+ Smoothing Type + + (2010,0080) + + U/U +
+ Requested Image Size + + (2020,0030) + + U/U +
+ Requested Decimate/Crop Behavior + + (2020,0040) + + U/U +
+
+
+ Status + The status values that are specific for this SOP Class are defined as follows. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values for Basic Color Image Box SOP Class
+ + Status + + + + Meaning + + + + Code + +
+ Warning + + Image size larger than image box size, the image has been demagnified. + + B604 +
+ + Image size is larger than the Image Box size. The Image has been cropped to fit. + + B609 +
+ + Image size or Combined Print Image size is larger than the Image Box size. The Image or Combined Print Image has been decimated to fit. + + B60A +
+ Failure + + Image size is larger than image box size + + C603 +
+ + Insufficient memory in printer to store the image + + C605 +
+ + Combined Print Image size is larger than the Image Box size + + C613 +
+
+
+ Behavior + The SCU uses the N-SET to request the SCP to update a Basic Color Image Box SOP Instance. The SCU shall only specify the SOP Instance UID of a Basic Color Image Box belonging to the last created Film Box SOP Instance and shall specify the list of Attributes for which the Attribute Values are to be set. + To instruct the SCP to erase the image in the image position, the SCU shall set a zero length and no value in the Attribute Basic Color Image Sequence (2020,0111). + The SCP shall set new values for the specified Attributes of the specified SOP Instance. + + The image in this N-SET supersedes any image previously set in the Image Box. + + The SCP shall return the status code of the requested SOP Instance update. The meaning of success, warning, and failure status codes is defined in . + If Requested Decimate/Crop Behavior (2020,0040) specifies DECIMATE, Magnification Type (2010,0060) specifies NONE, and the image is too large to fit the Image Box, the SCP shall fail the N-SET. + The color characteristics of the Pixel Data (7FE0,0010) in the Basic Color Image Box may be described by an ICC Input Device Profile specified in the Film Box, in which case the same profile shall apply to all the Image Boxes in the same Film Box. See and . +
+
+
+
+ SOP Class Definition and UID + The Basic Color Image Box SOP Class UID shall have the value "1.2.840.10008.5.1.1.4.1". +
+
+
+ Referenced Image Box SOP Class (Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-1998. +
+
+
+ Basic Annotation Box SOP Class +
+ IOD Description + The Basic Annotation Box IOD is an abstraction of the presentation of an annotation (e.g., text string) on a film. The Basic Annotation Box IOD describes the most used text related presentation parameters. + The Basic Annotation Box SOP Instance is created by the SCP at the time the Basic Film Box SOP Instance is created, based on the value of the Attribute Annotation Display Format ID (2010,0030) of the Basic Film Box. +
+
+ DIMSE Service Group + The DIMSE Services that are applicable to the IOD are shown below. + + + + + + + + + + + + + + +
DIMSE Services Applicable to Basic Annotation Box
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-SET + + U/M +
+ The meaning of the Usage SCU/SCP is described in . + + There is no N-CREATE because the Instances of the Basic Annotation Box SOP Class are created by the Film Box SOP Instance. + + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-SET + The N-SET is used to update the Basic Annotation Box SOP Instance. +
+ Attributes + The Attributes that may be updated are shown in . + + + + + + + + + + + + + + + + + + + + + +
N-SET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Annotation position + + (2030,0010) + + M/M +
+ Text String + + (2030,0020) + + U/M +
+ The meaning of the Usage SCU/SCP is described in . +
+
+ Status + There are no specific status codes. +
+
+ Behavior + The SCU uses the N-SET to request the SCP to update a Basic Annotation Box SOP Instance. The SCU shall only specify the SOP Instance UID of the Basic Annotation Box belonging to the last created Film Box SOP Instance in the N-SET request primitive, and shall specify the list of Attributes for which the Attribute Values are to be set. The SCU may erase the text string by setting a zero length value in the Attribute Text String (2030,0020). + The SCP shall set new values for the specified Attributes of the specified SOP Instance. + The SCP shall return the status code of the requested SOP Instance update. The meaning of success, warning, and failure status codes is defined in . +
+
+
+
+ SOP Class Definition and UID + The Basic Annotation Box SOP Class UID shall have the value "1.2.840.10008.5.1.1.15". +
+
+
+ Print Job SOP Class +
+ IOD Description + The Print Job IOD is an abstraction of the Print Job transaction and is the basic information entity to monitor the execution of the Print Process. A Print Job contains one film or multiple films, all belonging to the same film session. + The Print Job SOP Class is created by N-ACTION operation of the Film Session SOP Class, Film Box SOP Class, or Pull Print Request SOP Class. The Print Job SOP Instance is deleted after the films are printed or after a failure condition. +
+
+ DIMSE Service Group + The DIMSE Services that are applicable to the IOD are shown below. + + + + + + + + + + + + + + + + + + +
DIMSE Services Applicable to Print Job
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-EVENT-REPORT + + M/M +
+ N-GET + + U/M +
+ The meaning of the Usage SCU/SCP is described in . + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-EVENT-REPORT + The N-EVENT-REPORT is used to report execution status changes to the SCU in an asynchronous way. +
+ Attributes + The arguments of the N-EVENT-REPORT are defined as shown in . + + The encoding of Notification Event Information is defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Notification Event Information
+ + Event Type Name + + + + Event Type ID + + + + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Pending + + 1 + + Execution Status Info + + (2100,0030) + + U/M +
+ + + Film Session Label + + (2000,0050) + + U/U +
+ + + Printer Name + + (2110,0030) + + U/U +
+ Printing + + 2 + + Execution Status Info + + (2100,0030) + + U/M +
+ + + Film Session Label + + (2000,0050) + + U/U +
+ + + Printer Name + + (2110,0030) + + U/U +
+ Done + + 3 + + Execution Status Info + + (2100,0030) + + U/M +
+ + + Film Session Label + + (2000,0050) + + U/U +
+ + + Printer Name + + (2110,0030) + + U/U +
+ Failure + + 4 + + Execution Status Info + + (2100,0030) + + U/M +
+ + + Film Session Label + + (2000,0050) + + U/U +
+ + + Printer Name + + (2110,0030) + + U/U +
+
+
+ Behavior + The SCP uses the N-EVENT-REPORT to inform the SCU about each execution change. The SCP shall only use the N-EVENT-REPORT within the context of the Association in which the Print Job SOP Instance was created. + + If SCU wants to monitor the complete execution process of a Print Job, then the SCU should only release the Association after the receipt of the event type Done or Failure. + + The SCU shall return the confirmation from the N-EVENT-REPORT operation. + If the Event Type Name = Failure or Pending then the error/pending condition is stored in the Execution Status Info argument. The possible values of the Execution Status Info argument are defined in . + If the Event Type Name = Failure or Done then the SCP shall delete the Print Job SOP Instance after receiving a confirmation from the SCU. +
+
+
+ N-GET + The N-GET is used to retrieve an instance of the Print Job SOP Class. +
+ Attributes + The Attributes that may be retrieved are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-GET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Execution Status + + (2100,0020) + + U/M +
+ Execution Status Info + + (2100,0030) + + U/M +
+ Print Priority + + (2000,0020) + + U/M +
+ Creation Date + + (2100,0040) + + U/U +
+ Creation Time + + (2100,0050) + + U/U +
+ Printer Name + + (2110,0030) + + U/U +
+ Originator + + (2100,0070) + + U/U +
+ The meaning of the Usage SCU/SCP is described in . +
+
+ Behavior + The SCU uses the N-GET to request the SCP to get a Print Job SOP Instance. The SCU shall specify in the N-GET request primitive the UID of the SOP Instance to be retrieved. + The SCP shall return the values for the specified Attributes of the specified SOP Instance. + The SCP shall return the status code of the requested SOP Instance retrieval. The meaning of success, warning, and failure status codes is defined in . +
+
+
+
+ Execution Status Information + Status Information is defined in . Implementation specific warning and error codes shall be defined in the Conformance Statement. +
+
+ SOP Class Definition and UID + The Print Job SOP Class UID shall have the value "1.2.840.10008.5.1.1.14". +
+
+
+ Printer SOP Class +
+ IOD Description + The Printer IOD is an abstraction of the hard copy printer and is the basic Information Entity to monitor the status of the printer. + The Printer SOP Instance is created by the SCP during start-up of the hard copy printer and has a well-known SOP Instance UID. +
+
+ DIMSE Service Group + The DIMSE Services that are applicable to the IOD are shown below. + + + + + + + + + + + + + + + + + + +
DIMSE Services Applicable to Printer
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-EVENT-REPORT + + M/M +
+ N-GET + + U/M +
+ The meaning of the Usage SCU/SCP is described in . + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-EVENT-REPORT + The N-EVENT-REPORT is used to report the changes of the printer status in an asynchronous way. +
+ Attributes + The arguments of the N-EVENT-REPORT are defined as shown in . + + The encoding of Notification Event Information is defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Notification Event Information
+ + Event Type Name + + + + Event Type ID + + + + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Normal + + 1 + + + +
+ Warning + + 2 + + Printer Status Info + + (2110,0020) + + U/M +
+ + + Film Destination + + (2000,0040) + + U/U +
+ + + Printer Name + + (2110,0030) + + U/U +
+ Failure + + 3 + + Printer Status Info + + (2110,0020) + + U/M +
+ + + Film Destination + + (2000,0040) + + U/U +
+ + + Printer Name + + (2110,0030) + + U/U +
+
+
+ Behavior + The SCP shall use the N-EVENT-REPORT to inform the SCU about each execution change. The SCP shall send the events to all SCUs with which the SCP has an Association that is using the printer for which the status changes. + The SCU shall return the confirmation of the N-EVENT-REPORT operation. + If the Event Type Name = Warning or Failure then the warning/failure condition is stored in the Printer Status Info argument. The possible values the Printer Status Info argument are defined in . +
+
+
+ N-GET + The N-GET is used to retrieve an instance of the Printer SOP Class. +
+ Attributes + The Attributes that may be retrieved are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-GET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Printer Status + + (2110,0010) + + U/M +
+ Printer Status Info + + (2110,0020) + + U/M +
+ Printer Name + + (2110,0030) + + U/U +
+ Manufacturer + + (0008,0070) + + U/U +
+ Manufacturer Model Name + + (0008,1090) + + U/U +
+ Device Serial Number + + (0018,1000) + + U/U +
+ Software Versions + + (0018,1020) + + U/U +
+ Date Last Calibration + + (0018,1200) + + U/U +
+ Last Calibration + + (0018,1201) + + U/U +
+ The meaning of the Usage SCU/SCP is described in . +
+
+ Behavior + The SCU uses the N-GET to request the SCP to get a Printer SOP Instance. The SCU shall specify in the N-GET request primitive the UID of the SOP Instance to be retrieved. + The SCP shall return the values for the specified Attributes of the specified SOP Instance. + The SCP shall return the status code of the requested SOP Instance retrieval. The meaning of success, warning, and failure status codes is defined in . +
+
+
+
+ Printer Status Information + Status Information is defined in . Implementation specific warning and error codes shall be defined in the Conformance Statement. +
+
+ SOP Class Definition and UID + The Printer SOP Class UID shall have the value "1.2.840.10008.5.1.1.16". +
+
+ Reserved Identifications + The well-known UID of the Printer SOP Instance shall have the value "1.2.840.10008.5.1.1.17". +
+
+
+ VOI LUT Box SOP Class(Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-1998. +
+
+ Image Overlay Box SOP Class(Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-1998. +
+
+ Presentation LUT SOP Class +
+ Information Object Description + The Presentation LUT Information Object is an abstraction of a Presentation LUT (see ). The objective of the Presentation LUT is to realize image display tailored for specific modalities, applications, and user preferences. It is used to prepare image pixel data for display on devices that conform to the Grayscale Standard Display Function defined in . + + The density range to be printed, Min Density to Max Density, is specified at either the Film Box or the Image Box. As follows from the definition for Min Density and Max Density in , if the requested minimum density is lower than the minimum printer density, or the requested maximum density is greater than the maximum printer density, the printer will use its minimum or maximum density, respectively, when computing the standard response. + + The output of the Presentation LUT is Presentation Values (P-Values). P-Values are approximately related to human perceptual response. They are intended to facilitate common input for both hardcopy and softcopy display devices. P-Values are intended to be independent of the specific class or characteristics of the display device. + The Presentation LUT is not intended to alter the appearance of the pixel values, as specified as specified by the Photometric Interpretation (0028,0004) and Polarity (2020,0020). + The Basic Film Box Information Object, the Basic Image Box Information Object and the Referenced Image Box Object reference the Presentation LUT. + If the Configuration Information Attribute (2010,0150) of the Basic Film Box IOD contains information similar to the Presentation LUT, then the Presentation LUT Attributes shall take precedence. +
+ Mapping of P-Values to Optical Density + The mathematical definition of the Grayscale Standard Display Function and mapping of P-Values to optical density for reflective and transmissive printers is contained in . +
+
+
+ DIMSE Service Group + The following DIMSE Services are applicable to the association related Presentation LUT Information Object: + + + + + + + + + + + + + + + + + + +
DIMSE Services Are Applicable to Presentation LUT
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-DELETE + + U/M +
+ The meaning of the Usage SCU/SCP is described in section . + This section describes the behavior of the DIMSE Services, which are specific for this Information Object. The general behavior of the DIMSE services is specified in . +
+ N-CREATE + The N-CREATE Service Element is used to create an instance of the Presentation LUT SOP Class. +
+ Attributes + The Attribute list of the N-CREATE Service Element is defined as shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-CREATE Attribute List
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Presentation LUT Sequence + + (2050,0010) + + MC/M + (Required if Presentation LUT Shape (2050,0020) is not present. Not allowed otherwise.) +
+ >LUT Descriptor + + (0028,3002) + + MC/M + (Required if sequence is present). + See . +
+ >LUT Explanation + + (0028,3003) + + U/U +
+ >LUT Data + + (0028,3006) + + MC/M + (Required if sequence is present) +
+ Presentation LUT Shape + + (2050,0020) + + MC/M + (Required if Presentation LUT Sequence (2050,0010) is not present. Not allowed otherwise.) + SCPs shall support the Enumerated Values IDENTITY and LIN OD +
+
+ LUT Descriptor + The first value (number of entries in the LUT) shall be equal to: + + + 256 if Bits Stored = 8, + + + 4096 if Bits Stored = 12. + + + The second value shall be equal to 0. + The third value (number of bits for each LUT entry) shall be 10-16. + See the definition in for further explanation. +
+
+
+ Status + The status values that are specific for this SOP Class are defined as follows: + + + + + + + + + + + + + + + + + + + + + +
Status Values for Presentation LUT SOP Class
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Presentation LUT successfully created + + 0000 +
+ Warning + + Requested Min Density or Max Density outside of printer's operating range. The printer will use its respective minimum or maximum density value instead. + + B605 +
+
+
+ Behavior + The SCU uses the N-CREATE Service Element to request the SCP to create a Presentation LUT SOP Instance. The SCU shall initialize Attributes of the SOP Class as specified in section . + The SCU shall create the Presentation LUT prior to referencing it from the Film Box or the Image Box. + The Presentation LUT persists in the SCP as long as the Association in which it was created is open or an explicit N-DELETE is issued by the SCU. + The SCP shall return the status code of the requested SOP Instance creation. The meaning of success, warning, and failure status codes is defined in . + The SCP shall use the Grayscale Standard Display Function as specified in to convert the output of the Presentation LUT to density for printing. If the SCU specifies values for Illumination (2010,015E) and/or Reflected Ambient Light (2010,0160), these values shall be used instead of the default or configured values of the SCP. If these values are not supplied, the SCP shall use its default or configured values. (see for suggested defaults). +
+
+
+ N-DELETE + The N-DELETE Service Element is used to delete the Presentation LUT SOP Instance. +
+ Status + There are no specific error codes +
+
+ Behavior + The SCU uses the N-DELETE Service Element to request the SCP to delete the Presentation LUT SOP Instance. The SCU shall specify the Presentation LUT SOP Instance UID. + The SCP shall not delete a Presentation LUT SOP Instance as long as there are outstanding references to it. Otherwise, it shall delete the specified Presentation LUT SOP Instance. The N-DELETE of a Presentation LUT will prevent the SCU from further referencing it. The SCU shall not reference a previously deleted Presentation LUT. The SCP shall return the status code of the requested Presentation LUT SOP Instance deletion. The meaning of success, warning, and failure status codes is defined in . +
+
+
+ SOP Class Definition and UID + The Presentation LUT SOP Class UID is "1.2.840.10008.5.1.1.23". +
+
+
+
+ Pull Print Request SOP Class(Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-2004. +
+
+ Printer Configuration Retrieval SOP Class +
+ IOD Description + The Printer Configuration IOD is an abstraction of the hard copy printer and is the basic Information Entity to retrieve key imaging characteristics of the printer + The Printer Configuration Retrieval SOP Instance is created by the SCP during start-up of the hard copy printer and has a well-known SOP Instance UID. +
+
+ DIMSE Service Group + The DIMSE Services that are applicable to the IOD are shown in . + + + + + + + + + + + + + + +
IOD DIMSE Services
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-GET + + M/M +
+ The meaning of the Usage SCU/SCP is described in . + This Section describes the behavior of the DIMSE Service that are specific for this IOD. The general behavior of the DIMSE Services is specified in . +
+ N-GET + The N-GET is used to retrieve an instance of the Printer Configuration Retrieval SOP Class. +
+ Attributes + The Attributes that are retrieved are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-GET Attributes
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Printer Configuration Sequence + + (2000,001E) + + U/M +
+ >SOP Classes Supported + + (0008,115A) + + -/M +
+ >Maximum Memory Allocation + + (2000,0061) + + -/M +
+ >Memory Bit Depth + + (2000,00A0) + + -/M +
+ >Printing Bit Depth + + (2000,00A1) + + -/M +
+ >Media Installed Sequence + + (2000,00A2) + + -/M +
+ >>Item Number + + (0020,0019) + + -/M +
+ >>Medium Type + + (2000,0030) + + -/M +
+ >>Film Size ID + + (2010,0050) + + -/M +
+ >>Min Density + + (2010,0120) + + -/MC + Required if Sequence is Present and Min Density is known +
+ >>Max Density + + (2010,0130) + + -/M +
+ >Other Media Available Sequence + + (2000,00A4) + + -/M +
+ >>Medium Type + + (2000,0030) + + -/M +
+ >>Film Size ID + + (2010,0050) + + -/M +
+ >>Min Density + + (2010,0120) + + -/MC + Required if Sequence is Present and Min Density is known +
+ >>Max Density + + (2010,0130) + + -/M +
+ >Supported Image Display Formats Sequence + + (2000,00A8) + + -/M +
+ >>Rows + + (0028,0010) + + -/MC + Required if all Image Boxes in the Display Format have the same number of rows and columns +
+ >>Columns + + (0028,0011) + + -/MC + Required if all Image Boxes in the Display Format have the same number of rows and columns +
+ >>Image Display Format + + (2010,0010) + + -/M +
+ >>Film Orientation + + (2010,0040) + + -/M +
+ >>Film Size ID + + (2010,0050) + + -/M +
+ >>Printer Resolution ID + + (2010,0052) + + -/M +
+ >>Printer Pixel Spacing + + (2010,0376) + + -/M +
+ >>Requested Image Size Flag + + (2020,00A0) + + -/M +
+ >Default Printer Resolution ID + + (2010,0054) + + -/M +
+ >Default Magnification Type + + (2010,00A6) + + -/M +
+ >Other Magnification Types Available + + (2010,00A7) + + -/M +
+ >Default Smoothing Type + + (2010,00A8) + + -/M +
+ >Other Smoothing Types Available + + (2010,00A9) + + -/M +
+ >Configuration Information Description + + (2010,0152) + + -/M +
+ >Maximum Collated Films + + (2010,0154) + + -/M +
+ >Decimate/Crop Result + + (2020,00A2) + + -/M +
+ >Manufacturer + + (0008,0070) + + -/M +
+ >Manufacturer Model Name + + (0008,1090) + + -/M +
+ >Printer Name + + (2110,0030) + + -/M +
+ The meaning of the Usage SCU/SCP is described in . +
+
+ Behavior + The SCU uses the N-GET to request the SCP to get a Printer Configuration Retrieval SOP Instance. The SCU shall specify in the N-GET request primitive the UID of the SOP Instance to be retrieved. + The SCP shall return the values for the specified Attributes of the specified SOP Instance. + The SCP shall return the status code of the requested SOP Instance retrieval. + A Failure status code shall indicate that the SCP has not retrieved the SOP Instance. +
+
+
+
+ SOP Class Definition and UID + The Printer Configuration Retrieval SOP Class UID is "1.2.840.10008.5.1.1.16.376". +
+
+ Reserved Identifications + The well-known UID of the Printer Configuration Retrieval SOP Instance is "1.2.840.10008.5.1.1.17.376". +
+
+
+ Basic Print Image Overlay Box SOP Class(Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-2004. +
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation procedure is used to negotiate the supported SOP Classes or Meta SOP Classes. specifies the Association procedures. + The negotiation procedure is used to negotiate the supported Meta SOP Classes and the supported optional SOP Classes. The SCU and SCP shall support at least one Meta SOP Class UID (e.g., Basic Grayscale Print Management Meta SOP Class) and may support additional optional SOP Classes. + The Print Management Service Class does not support extended negotiation. + The SCU shall specify in the A-ASSOCIATE request one Abstract Syntax, in a Presentation Context, for each supported SOP Class or Meta SOP Class. + If the Association is released or aborted then all the SOP Instances except the Print Job SOP Instance and the Printer SOP Instance are deleted. + + Pending Print Jobs will still be printed after the release or abortion of the Association. + +
+
+ Example of Print Management SCU Session (Informative) +
+ Simple Example + Moved to . +
+
+ Advanced Example(Retired) + This section was previously defined in DICOM. It is now retired. See PS 3.4-1998. +
+
+
+ Example of the Pull Print Request Meta SOP Class (Informative) + This section was previously defined in DICOM. It is now retired. See PS 3.4-2004. +
+
+ Overlay Examples (Informative) + This section was previously defined in DICOM. It is now retired. See PS 3.4-2004. +
+
+ + Media Storage Service Class (Normative) +
+ Overview +
+ Scope + The Media Storage Service Class defines an application-level class-of-service that facilitates the simple transfer of images and associated information between DICOM AEs by means of Storage Media. It supports: + + + The Interchange of images and a wide range of associated information. + + +
+
+ Service Definition + DICOM AEs implement a SOP Class of the Media Storage Service Class by supporting one or more roles among the three roles FSC, FSR or FSU. SOP Classes of the Media Storage Service Class are implemented using the Media Storage Operations (M-WRITE, M-READ, M-DELETE, M-INQUIRE FILE-SET and M-INQUIRE FILE). The services provided by these Operations are defined in . +
+
+
+ Behavior + This Section discusses the FSC, FSR and FSU behavior for SOP Classes of the Media Storage Service Class. +
+ Behavior of an FSC + The FSC shall be able to create a DICOMDIR File containing the Media Storage Directory SOP Class for the created File-set and create zero or more Files belonging to the File-set by invoking M-WRITE Operations with SOP Instances that meet the requirements of the corresponding IOD. It is the responsibility of the FSC to ensure that the M-WRITE results in the creation of a correctly formatted DICOM File. The manner in which this is achieved is beyond the scope of the DICOM Standard. + The FSC shall support the Media Storage Operation M-INQUIRE FILE-SET and may optionally support the M-INQUIRE FILE. +
+
+ Behavior of an FSR + The FSR shall be able to recognize a File-set and the corresponding DICOMDIR containing the Media Storage Directory SOP Class. A valid File-set may contain only a DICOMDIR and no other files. If a File-set contains other files with stored SOP Instance, the FSR shall be capable of invoking M-READ Operations to access the content of the Files of the File-set. The manner in which this is achieved is beyond the scope of the DICOM Standard. + The FSR shall support the Media Storage Operation M-INQUIRE FILE and may optionally support the M-INQUIRE FILE-SET. +
+
+ Behavior of an FSU + The FSU shall be able to recognize a File-set and the corresponding DICOMDIR containing the Media Storage Directory SOP Class. A valid File-set may contain only a DICOMDIR and no other files. If a File-set contains other files with stored SOP Instances, the FSU shall be capable of invoking M-READ Operations to access the content of the Files of the File-set. The manner in which this is achieved is beyond the scope of the DICOM Standard. + The FSU shall support the Media Storage Operation M-INQUIRE FILE and the M-INQUIRE FILE-SET. + The FSU shall be able to create one or more new Files belonging to the File-set by invoking M-WRITE Operations with SOP Instances that meet the requirements of the corresponding IOD. It is the responsibility of the FSU to ensure that the M-WRITE results in the creation of a correctly formatted DICOM File. The manner in which this is achieved is beyond the scope of the DICOM Standard. The FSU shall be able to update the contents of the DICOMDIR File by using M-DELETE and or M-WRITE Operations. +
+
+
+ Conformance +
+ Conformance as an FSC + An implementation that conforms to one of the SOP Classes of the Media Storage Service Class: + + + shall meet the requirements specified in ; + + + shall meet the requirements specified in ; + + + shall perform M-WRITE Operations according to the SOP Class specification identified by the SOP Class UID in the Meta File Information; + + + shall support the Media Storage Directory SOP Class (stored in the DICOMDIR File). + + +
+
+ Conformance as an FSR + An implementation that conforms to one of the SOP Classes of the Media Storage Service Class: + + + shall meet the requirements specified in ; + + + shall meet the requirements specified in ; + + + shall perform M-READ Operations according to the SOP Class specification identified by the SOP Class UID in the Meta File Information. M-READ of non-supported SOP Classes shall simply result in ignoring such stored Data Sets; + + + shall read DICOMDIR Files without a Directory Information Module or with a Directory Information Module including Directory Records of a Type not supported by the implementation. + + +
+
+ Conformance as an FSU + An implementation that conforms to one of the SOP Classes of the Media Storage Service Class: + + + shall meet the requirements specified in ; + + + shall meet the requirements specified in ; + + + shall perform M-READ Operations according to the SOP Class specification identified by the SOP Class UID in the Meta File Information. M-READ of unsupported SOP Classes shall simply result in ignoring such stored Data Sets; + + + shall perform M-WRITE Operations according to the SOP Class specification identified by the SOP Class UID in the Meta File Information; + + + shall support the Media Storage Directory SOP Class (stored in the DICOMDIR File). Directories containing a Directory Information Module shall be updated by an FSU. Directories containing no Directory Information Module shall not be updated by an FSU; + + + shall read DICOMDIR Files without a Directory Information Module or with a Directory Information Module including Directory Records of a Type not supported by the implementation. + + +
+
+ Conformance Statement Requirements + An implementation of the Media Storage Service Class may support one or more Roles as specified in . In addition, the implementation may conform to one or more of the SOP Classes of the Media Storage Service Class defined in . The Conformance Statement shall be in the format defined by . + + + + + + + + + + + + + + + + + + + + + + + + +
Allowed Combinations of Roles
+ + Roles + + + + FSR + + + + FSC + + + + FSU + +
+ With a Directory Information Module + + Allowed + + Allowed + + Allowed Directory shall be updated +
+ With no Directory Information Module + + Allowed + + Allowed + + Allowed Directory shall not be updated +
+ The following aspects shall be documented in the Conformance Statement of any implementation claiming conformance to one of the Media Storage SOP Classes: + + + the subset of the Basic Directory Information Object Model supported; + + + When the Directory Information Module is created or updated (Directory Information Module supported), the optional standard keys that may be included in Directory Records shall be documented. Private Keys and Private Records may also be documented; + + +
+
+ Standard Extended, Specialized, and Private Conformance + In addition to Standard Media Storage SOP Classes, implementations may support Standard Extended, Specialized and/or Private SOP Classes as defined by . + For all three types of SOP Classes, implementations shall be permitted to conform as an FSC, FSR, both or as an FSU. The Conformance Statement shall be in the format defined in . +
+
+
+ Media Storage Standard SOP Classes + The SOP Classes in the Media Storage Service Class identify the Composite IODs to be stored. The following Standard SOP Classes are defined: + + + all SOP Classes specified for the DIMSE C-STORE based Storage Service Class identified in + + + + all SOP Classes specified for the DIMSE C-STORE based Non-Patient Object Storage Service Class identified in + + + + the media directory SOP Class identified in + + + + + + + + + + + + + + + + + + + +
Media Storage Standard SOP Classes
+ + SOP Class Name + + + + SOP Class UID + + + + IOD Specification (defined in ) + +
+ Media Storage Directory Storage + + 1.2.840.10008.1.3.10 + + + + +
+ + + + Except for the Media Storage Directory SOP Class, all the SOP Classes in the Media Storage Service Class are assigned the same UID Value as the corresponding network communication SOP Classes. This was done to simplify UID assignment. Although these SOP Classes are based on different Operations, the context of their usage should unambiguously distinguish a SOP Class used for Media Storage from a network communication SOP Class. + + + The storage of Normalized Print SOP Instances on media was previously defined in DICOM. They have been retired. See PS 3.4-1998. + + + The storage of Detached and Standalone SOP Instances on media was previously defined in DICOM. They have been retired. See PS 3.4-2004 + + + +
+ Specialization for Standard SOP Classes +
+ Softcopy Presentation State Storage SOP Classes + See . +
+
+ Structured Reporting Storage SOP Classes + The requirements of apply to the following SOP Classes: + + + Basic Text SR + + + Enhanced SR + + + Comprehensive SR + + + Comprehensive 3D SR + + + Extensible SR + + + Mammography CAD SR + + + Chest CAD SR + + + Procedure Log + + + X-Ray Radiation Dose SR + + + Radiopharmaceutical Radiation Dose SR + + + Patient Radiation Dose SR + + + Spectacle Prescription Report + + + Colon CAD SR + + + Macular Grid Thickness and Volume Report + + + Implantation Plan SR Document + + + Acquisition Context SR + + + + requirements do not apply to the Key Object Selection Document SOP Class. +
+
+
+
+ Retired Standard SOP Classes + See . +
+
+ + Storage Commitment Service Class (Normative) +
+ Overview +
+ Scope + The mechanism currently defined in DICOM for network based storage of SOP Instances, the Storage Service Class, allows a Service Class User (SCU) to transmit images and other Composite SOP Instances to a Service Class Provider (SCP). However, the Storage Service Class does not specify that the SCP explicitly take responsibility for the safekeeping of data into account. That is, there is no commitment that the SCP will do more than accept the transmitted SOP Instances. In order to have medical image management in addition to medical image communication, there is a need for a Service Class within DICOM that ensures that there is an explicitly defined commitment to store the SOP Instances. + The Storage Commitment Service Class defines an application-level class-of-service that facilitates this commitment to storage. The Storage Commitment Service Class enables an Application Entity (AE) acting as an SCU to request another Application Entity (AE) acting as an SCP to make the commitment for the safekeeping of the SOP Instances (i.e., that the SOP Instances will be kept for an implementation specific period of time and can be retrieved). The AE where such SOP Instances can later be retrieved may be the SCP where storage commitment was accepted or it may be distinct from that SCP. + The SCP implementation defines how it provides its commitment to storage. Certain SCPs may commit to permanently store the SOP Instances (e.g., an archive system) while other SCPs may commit to provide storage of the SOP Instances for a limited amount of time. The SCP is required to document in its Conformance Statement the nature of its commitment to storage (e.g., duration of storage, retrieve capabilities and latency, capacity). + The possession of a link to access pixel data shall not be sufficient for the SCP to commit to storage. A copy of the entire pixel data is required. + + This situation may arise in the context of a JPIP Referenced Pixel Data Transfer Syntax. + + Once the SCP has accepted the commitment to store the SOP Instances, the SCU may decide that it is appropriate to delete its copies of the SOP Instances. These types of policies are outside the scope of this Standard, however, the SCU is required to document these policies in its Conformance Statement. +
+
+ Models Overview + The request for storage commitment can be accomplished using the Push Model. + The Push model expects an SCU to transmit SOP Instances to an SCP using an appropriate mechanism outside the scope of this Service Class. Storage commitment is then initiated by transmitting a Storage Commitment Request containing references to a set of one or more SOP Instances. Success or failure of storage commitment is subsequently indicated via a notification from the SCP to the SCU. + + + + A Pull Model was defined in earlier versions, but has been retired. See PS 3.4-2001. + + + As indicated, the mechanisms used to transfer SOP Instances from an SCU to an SCP are outside the scope of this Service Class. However, typical mechanisms are found in the Storage Service Class, the Query/Retrieve Service Class, or Media Exchange. + + + +
+
+
+ Conformance Overview + The application-level services addressed by this Service Class are specified via the Storage Commitment Push Model SOP Class.: + An SCP implementation of the Storage Commitment Service Class shall support the Storage Commitment Push Model SOP Class. + The SOP Class specifies Attributes, operations, notifications, and behavior applicable to the SOP Class. The conformance requirements shall be specified in terms of the Service Class Provider (SCP) and the Service Class User (SCU). + The Storage Commitment Service Class uses the Storage Commitment IOD as defined in and the N-ACTION and N-EVENT-REPORT DIMSE Services specified in . +
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation rules as specified in shall be used to negotiate the supported SOP Classes. + Support for the SCP/SCU role selection negotiation is mandatory. The SOP Class Extended Negotiation shall not be supported. + An SCP implementation of the Storage Commitment Service Class shall support the Storage Commitment Push Model SOP Class. +
+
+
+ Storage Commitment Push Model SOP Class + The Storage Commitment Push Model SOP Class is intended for those Application Entities requiring storage commitment where the SCU determines the time at which the SOP Instances are transmitted. The SCU transmits the SOP Instances to the SCP using an appropriate mechanism. The request for storage commitment is transmitted to the SCP together with a list of references to one or more SOP Instances. Success or failure of storage commitment is subsequently indicated by a notification from the SCP to the SCU. +
+ DIMSE Service Group + The DIMSE-N Services applicable to the Storage Commitment Push Model SOP Class are shown in . + + + + + + + + + + + + + + + + + + +
IOD DIMSE Services
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-EVENT-REPORT + + M/M +
+ N-ACTION + + M/M +
+ The DIMSE-N Services and Protocol are specified in . +
+
+ Operations + The DICOM AEs that claim conformance to this SOP Class as an SCU shall invoke the N-ACTION operation. The DICOM AEs that claim conformance to this SOP Class as an SCP shall support the N-ACTION operation. +
+ Storage Commitment Request + The Storage Commitment Request operation allows an SCU to request an SCP to commit to the safekeeping of a set of SOP Instances. This operation shall be invoked through the N-ACTION primitive. +
+ Action Information + The DICOM AEs that claim conformance to this SOP Class as an SCU and/or an SCP shall support the Action Types and Action Information as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Storage Commitment Request - Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Request Storage Commitment + + 1 + + Transaction UID + + (0008,1195) + + 1/1 +
+ + + Storage Media File-Set ID + + (0088,0130) + + 3/3 + See . +
+ + + Storage Media File-Set UID + + (0088,0140) + + 3/3 + See . +
+ + + Referenced SOP Sequence + + (0008,1199) + + 1/1 +
+ + + >Referenced SOP Class UID + + (0008,1150) + + 1/1 +
+ + + >Referenced SOP Instance UID + + (0008,1155) + + 1/1 + See . +
+ + + >Storage Media File-Set ID + + (0088,0130) + + 3/3 + See . +
+ + + >Storage Media File-Set UID + + (0088,0140) + + 3/3 + See . +
+
+ Storage Media File Set ID Attributes + If present, the Storage Media File-Set ID (0088,0130) and Storage Media File-Set UID (0088,0140) shall appear either outside the Referenced SOP Sequence (0008,1199), or within one or more Items within that sequence, but not both. If they appear outside of the sequence, then all of the SOP Instances within the sequence shall be retrievable from the specified Storage Media File-Set. If they appear within an Item of that sequence, then the SOP Instance referenced to by that Item shall be retrievable from the specified Storage Media File-Set. +
+
+ Referenced Performed Procedure Step Sequence Attribute (Retired) + Referenced Performed Procedure Step Sequence (0008,1111) was included in earlier versions, but its use here has been retired. See PS 3.4-2001, in which the Attribute was formerly known as Referenced Study Component Sequence. + + This section formerly specified a means of referencing a Study Component that has been completed and semantics that the list of images in the commitment request represented a complete set. This section has been retired since the Modality Performed Procedure Step SOP Classes provide the same facility in a more appropriate service. + +
+
+ SOP Instance Reference + A SOP Instance may be referenced only once within the Referenced SOP Sequence (0008,1199). +
+
+
+ Service Class User Behavior + The SCU shall use the N-ACTION primitive to request the SCP the safekeeping of a set of SOP Instances. The SOP Instances are referenced in the Action Information as specified in . The Action Type ID shall be set to 1 specifying the request for storage commitment. + The SCU shall supply the Transaction UID Attribute (0008,1195) to uniquely identify each Storage Commitment Request. The value of the Transaction UID Attribute will be included by the SCP in the Storage Commitment Result (see ). Use of the Transaction UID Attribute allows the SCU to match requests and results that may occur over the same or different Associations. + The N-ACTION primitive shall contain the well-known Storage Commitment Push Model SOP Instance UID (defined in ) in its Requested SOP Instance UID parameter. + + In the usage described here, there is no explicit creation of a SOP Instance upon which an N-ACTION primitive may operate. Instead, the N-ACTION primitive operates upon a constant well-known SOP Instance. This SOP Instance is conceptually created during start up of each Storage Commitment Service Class SCP Application. + + Upon receipt of a successful N-ACTION Response Status Code from the SCP, the SCU now knows that the SCP has received the N-ACTION request. Upon receipt of any other N-ACTION Response Status Code from the SCP, the SCU now knows that the SCP will not process the request and therefore will not commit to the storage of the SOP Instances referenced by the Storage Commitment Request. The actions taken by the SCU upon receiving the status is beyond the scope of this Standard. Upon receipt of a failure status, the Transaction UID is no longer active and shall not be reused for other transactions. + At any time after receipt of the N-ACTION-Response, the SCU may release the association on which it sent the N-ACTION-Request. + + + + Failure of storage commitment will be signaled via the N-EVENT-REPORT primitive. + + + In situations where the SOP Instance(s) are transferred via Media Interchange, the Storage Commitment Request may fail because the piece of Media containing the referenced SOP Instance(s) may not yet have been read. Attributes (0088,0130) File-Set ID and (0088,0140) File-Set UID may or may not be present in the case of Media Interchange. They may be provided to facilitate identification of the media containing the transferred SOP Instance(s) by the Storage Commitment SCP. + + + +
+
+ Service Class Provider Behavior + Upon receipt of the N-ACTION request, the SCP shall return, via the N-ACTION response primitive, the N-ACTION Response Status Code applicable to the associated request. A success status conveys that the SCP has successfully received the request. A failure status conveys that the SCP is not processing the request. + + + + Failure of storage commitment will be signaled via the N-EVENT-REPORT primitive. + + + When a Storage Commitment Request is received by an SCP it may immediately assess the list of references for which Storage Commitment is requested and return an N-EVENT-REPORT. In situations where the SOP Instance(s) are transferred via Media Interchange, the N-EVENT-REPORT may fail because the piece of Media containing the referenced SOP Instance(s) may not yet have been read. Attributes (0088,0130) File-Set ID and (0088,0140) File-Set UID may or may not be present in the case of Media Interchange. They may be used to facilitate identification of the media containing the transferred SOP Instance(s) by the Storage Commitment SCP. + + + +
+
+ Status Codes + No Service Class specific status values are defined for the N-ACTION Service. See for general response status codes. +
+
+
+
+ Notifications + The DICOM AEs that claim conformance to this SOP Class as an SCP shall invoke the N-EVENT-REPORT request. The DICOM AEs that claim conformance to this SOP Class as an SCU shall be capable of receiving the N-EVENT-REPORT request. +
+ Storage Commitment Result + The Storage Commitment Result notification allows an SCP to inform the SCU whether or not it has accepted storage commitment responsibility for the SOP Instances referenced by a Storage Commitment Request. This notification is also used to convey error information (i.e., storage commitment could not be achieved for one or more of the referenced SOP Instances). This notification shall be invoked through the N-EVENT-REPORT primitive. +
+ Event Information + The DICOM AEs that claim conformance to this SOP Class as an SCU and/or an SCP shall support the Event Types and Event Information as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Storage Commitment Result - Event Information
+ + Event Type Name + + + + Event Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Storage Commitment Request Successful + + 1 + + Transaction UID + + (0008,1195) + + -/1 +
+ + + Retrieve AE Title + + (0008,0054) + + -/3 + See . +
+ + + Storage Media File-Set ID + + (0088,0130) + + -/3 + See . +
+ + + Storage Media File-Set UID + + (0088,0140) + + -/3 + See . +
+ + + Referenced SOP Sequence + + (0008,1199) + + -/1 +
+ + + >Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ + + >Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ + + >Retrieve AE Title + + (0008,0054) + + -/3 + See . +
+ + + >Storage Media File-Set ID + + (0088,0130) + + -/3 + See . +
+ + + >Storage Media File-Set UID + + (0088,0140) + + -/3 + See . +
+ Storage Commitment Request Complete - Failures Exist + + 2 + + Transaction UID + + (0008,1195) + + -/1 +
+ + + Retrieve AE Title + + (0008,0054) + + -/3 + See . +
+ + + Storage Media File-Set ID + + (0088,0130) + + -/3 + See . +
+ + + Storage Media File-Set UID + + (0088,0140) + + -/3 + See . +
+ + + Referenced SOP Sequence + + (0008,1199) + + -/1C + This Attribute shall be provided if storage commitment for one or more SOP Instances has been successful +
+ + + >Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ + + >Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ + + >Retrieve AE Title + + (0008,0054) + + -/3 + See . +
+ + + >Storage Media File-Set ID + + (0088,0130) + + -/3 + See . +
+ + + >Storage Media File-Set UID + + (0088,0140) + + -/3 + See . +
+ + + Failed SOP Sequence + + (0008,1198) + + -/1 +
+ + + >Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ + + >Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ + + >Failure Reason + + (0008,1197) + + -/1 +
+
+ Retrieve AE Title Attribute + If present, the Retrieve AE Title (0008,0054) shall appear either outside the Referenced SOP Sequence (0008,1199), or within one or more Items within that sequence, but not both. If they appear outside of the sequence, then all of the SOP Instances within the sequence shall be retrievable from the specified Retrieve AE Title. If they appear within an Item of that sequence, then the SOP Instance referenced to by that Item shall be retrievable from the specified Retrieve AE Title. +
+
+ Storage Media File Set ID Attributes + If present, the Storage Media File-Set ID (0088,0130) and Storage Media File-Set UID (0088,0140) shall appear either outside the Referenced SOP Sequence (0008,1199), or within one or more Items within that sequence, but not both. If they appear outside of the sequence, then all of the SOP Instances within the sequence shall be retrievable from the specified Storage Media File-Set. If they appear within an Item of that sequence, then the SOP Instance referenced to by that Item shall be retrievable from the specified Storage Media File-Set. +
+
+
+ Service Class Provider Behavior + If the SCP determines that it has successfully completed storage commitment for all the SOP Instances referenced by a Storage Commitment Request, the SCP shall issue an N-EVENT-REPORT with the Event Type ID set to 1 (storage commitment request successful). This event shall include references to the successfully stored SOP Instances. The SCP shall store the referenced SOP Instances in accordance with Level 2 as defined in the Storage Service Class (i.e., all Attributes, including Private Attributes). The Storage Service Class is defined in . After the N-EVENT-REPORT has been sent, the Transaction UID is no longer active and shall not be reused for other transactions. + If it is determined that storage commitment could not be achieved for one or more referenced SOP Instances, the SCP shall issue an N-EVENT-REPORT with the Event Type ID set to 2 (storage commitment request complete - failure exists) conveying that the SCP does not commit to store all SOP Instances. This event shall include references to the failed SOP Instances together with references to those SOP Instances that have been successfully stored. For each failed SOP Instance the reason for failure shall be described by the Failure Reason Attribute. After the N-EVENT-REPORT has been sent, the Transaction UID is no longer active and shall not be reused for other transactions. + The complete set of SOP Instances referenced by the Referenced SOP Sequence (0008,1199) Attribute, in the initiating N-ACTION, shall be present in both Event Types either in the Referenced SOP Sequence (0008,1199) or in the Failed SOP Sequence (0008,1198). + The N-EVENT-REPORT shall include the same Transaction UID Attribute (0008,1195) value as contained in the initiating N-ACTION. + An SCP shall be capable of issuing the N-EVENT-REPORT on a different association than the one on which the N-ACTION operation was performed. + + + + The SCP may attempt to issue the N-EVENT-REPORT on the same Association, but this operation may fail because the SCU is free to release at any time the Association on which it sent the N-ACTION-Request. As DICOM defaults the association requestor to the SCU role, the SCP (i.e., the association requester) negotiates an SCP role using the SCU/SCP role negotiation (see ). + + + When responding on a different Association, the SCP must use the same AE Title as it used on the original Association, because the DICOM Standard defines a Service between two peer applications, each identified by an AE Title. Thus the SCP should be consistently identified for all Associations in the particular instance of the Storage Commitment Service. + + + The optional Attributes Retrieve AE Title (0008,0054), Storage Media File-Set ID (0088,0130) and Storage Media File-Set UID (0088,0140) within the Event Information allows an SCP to indicate the location where it has stored SOP Instances for safekeeping. For example, the SCP could relay SOP Instances to a third Application Entity using this Service Class, in which case it can use the Retrieve AE Title Attribute to indicate the real location of the data. Another example is if the SCP stores data on media, it can indicate this using the Storage Media File-Set ID and UID Attributes. + + + +
+
+ Service Class User Behavior + An SCU shall be capable of receiving an N-EVENT-REPORT on a different association than the one on which the N-ACTION operation was performed. + + To receive this N-EVENT-REPORT, the SCU accepts an association where the SCP role is proposed by the Storage Commitment SCP acting as an association requestor. + + The SCU shall return, via the N-EVENT-REPORT response primitive, the N-EVENT-REPORT Response Status Code applicable to the associated request. The actions taken by the SCU upon receiving the N-EVENT-REPORT are beyond the scope of this Standard but are stated in its Conformance Statement. + + In the case where the SCP indicates that it cannot achieve storage commitment for some SOP Instances, the SCU might, for example, re-send the failed SOP Instances to the SCP (via the Storage Service Class) and then re-transmit the N-ACTION request. However, this behavior is beyond the scope of this Standard. + +
+
+ Status Codes + No Service Class specific status values are defined for the N-EVENT-REPORT Service. See for general response status codes. + + This Section refers to status codes returned by the N-EVENT-REPORT response primitive. The Failure Reason Attribute returned in the Storage Commitment Result - Event Information (see ) are described in the Storage Commitment IOD. + +
+
+
+
+ Storage Commitment Push Model SOP Class UID + The Storage Commitment Push Model SOP Class shall be uniquely identified by the Storage Commitment Push Model SOP Class UID, which shall have the value "1.2.840.10008.1.20.1". +
+
+ Storage Commitment Push Model Reserved Identification + The well-known UID of the Storage Commitment Push Model SOP Instance shall have the value "1.2.840.10008.1.20.1.1". +
+
+ Conformance Requirements + Implementations claiming Standard SOP Class Conformance to the Storage Commitment Push Model SOP Class shall be conformant as described in this Section and shall include within their Conformance Statement information as described in this Section and sub-Sections. + An implementation may claim conformance to this SOP Class as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that is conformant to this SOP Class as an SCU shall meet conformance requirements for + + + the operations and actions that it invokes + + + the notifications that it receives. + + + The mechanisms used by the SCU to transfer SOP Instances to the SCP shall be documented. +
+ Operations + The SCU shall document in the SCU Operations Statement the actions and behavior that cause the SCU to generate an N-ACTION primitive (Storage Commitment Request). + The SCU shall specify the SOP Class UIDs for which it may request storage commitment. + The SCU shall specify the duration of applicability of the Transaction UID. This may be specified as a time limit or a policy that defines the end of a transaction (e.g., how long will the SCU wait for a N-EVENT-REPORT). + The SCU shall specify if it supports the optional Storage Media File-Set ID & UID Attributes in the N-ACTION. If these Attributes are supported, the SCU shall also specify which Storage Media Application Profiles are supported. + The SCU Operations Statement shall be formatted as defined in + +
+
+ Notifications. + The SCU shall document in the SCU Notifications Statement the behavior and actions taken by the SCU upon receiving an N-EVENT-REPORT primitive (Storage Commitment Result). + The SCU shall specify the behavior and actions performed when a success status is received (i.e., if and when local SOP Instances copies are deleted). + The SCU shall specify the behavior and actions performed when a failure status is received (i.e., recovery mechanisms, etc.). + The SCU Notifications Statement shall be formatted as defined in + +
+
+
+ SCP Conformance. + An implementation that is conformant to this SOP Class as an SCP shall meet conformance requirements for + + + the operations and actions that it performs + + + the notifications that it generates. + + +
+ Operations + The SCP shall document in the SCP Operations Statement the behavior and actions of the SCP upon receiving the N-ACTION primitive (Storage Commitment Request). + The SCP shall specify parameters indicating the level of storage commitment, such as: + + + under what conditions the SCP would delete SOP Instances + + + persistence of storage + + + capacity + + + volatility + + + other pertinent information + + + The SCP shall specify the mechanisms and characteristics of retrieval of stored SOP Instances, such as: + + + supported query/retrieve services + + + latency + + + other pertinent information + + + The SCP shall specify if it supports the optional Storage Media File-Set ID & UID Attributes in the N-ACTION. If these Attributes are supported, the SCP shall also specify which Storage Media Application Profiles are supported. + The SCP Operations Statement shall be formatted as defined in + +
+
+ Notifications + The SCP shall document in the SCP Notifications Statement the behavior and actions that cause the SCP to generate an N-EVENT-REPORT primitive (Storage Commitment Result). + The SCP shall specify if it supports the optional Storage Media File-Set ID & UID Attributes in the N-EVENT-REPORT and describe the policies for how the Media is used. The SCP shall also specify which Storage Media Application Profiles are supported. + The SCP shall specify if it supports the optional Retrieve AE Title (0008,0054) Attribute in the N-EVENT-REPORT and describe the policies for how it is used. + The SCP Notifications Statement shall be formatted as defined in + +
+
+
+
+
+ Storage Commitment Pull Model SOP Class(Retired) + A Pull Model was defined in earlier versions, but has been retired. See PS 3.4-2001. +
+
+ Storage Commitment Examples (Informative) + Moved to + +
+
+ + Basic Worklist Management Service (Normative) +
+ Overview +
+ Scope + The Basic Worklist Management Service Class defines an application-level class-of-service that facilitates the access to worklists. + A worklist is the structure to present information related to a particular set of tasks. It specifies particular details for each task. The information supports the selection of the task to be performed first, and supports the performance of that task. + + One example is the worklist used to present information about scheduled imaging procedures at an imaging modality and to the operator of that modality. Another example is the worklist presented at a radiological reporting station to indicate which studies have been performed and are waiting to be reported. + + This annex defines a service for communicating such worklists. The following are characteristics for this Service Class: + + + The worklist has to be queried by the Application Entity (AE) associated with the application on which, or by which, the tasks included in the worklist have to be performed. In this query, a number of search keys can be used, defined for each particular worklist SOP class. + + + The worklist consists of worklist items, each item is related to one task. A worklist item contains Attributes from different objects related to the task. + + + + + + This Service Class is not intended to provide a comprehensive generalized database query mechanism such as SQL. Instead, the Basic Worklist Management Service Class is focused towards basic queries using a small set of common Key Attributes used as Matching Keys or Return Attributes. Basic Worklist Information Models are not hierarchical. + + + Basic Worklist Information Models always consist of one query level, which may consist of one or more entities. There is no distinction between hierarchical and relational use of C-Find in the Basic Worklist Management Service Class. + + + +
+
+ Conventions + Key Attributes serve two purposes, they may be used as: Matching Key Attributes and Return Key Attributes. Matching Key Attributes may be used for matching (criteria to be used in the C-FIND request to determine whether an entity matches the query). Return Key Attributes may be used to specify desired return Attributes (what elements in addition to the Matching Key Attributes have to be returned in the C-FIND response). + + Matching Keys are typically used in an SQL 'where' clause. Return Keys are typically used in an SQL 'select' clause to convey the Attribute values. + + Matching Key Attributes may be of Type "required" (R) or "optional" (O). Return Key Attributes may be of Type 1, 1C, 2, 2C, 3 as defined in . +
+
+ Worklist Information Model + In order to serve as a Service Class Provider (SCP) of the Basic Worklist Service Class, a DICOM Application Entity (AE) possesses information about the Attributes of a number of managed worklist entries. This information is organized into Worklist Information Models. + Worklists are implemented against well defined Information Models. A specific SOP Class of the Basic Worklist Service Class consists of an informative Overview, an Information Model Definition and a DIMSE-C Service Group. In this Service Class, the Information Model plays a role similar to an Information Object Definition (IOD) of most other DICOM Service Classes. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Basic Worklist Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Basic Worklist Service Class are implemented using the DIMSE-C C-FIND service as defined in . + Both a baseline and extended behavior are defined for the DIMSE-C C-FIND. Baseline behavior specifies a minimum level of conformance for all implementations to facilitate interoperability. Extended behavior enhances the baseline behavior to provide additional features that may be negotiated independently at Association establishment time. + The following description of the DIMSE-C C-FIND service provides a brief overview of the SCU/SCP semantics. + A C-FIND service conveys the following semantics: + + + The SCU requests that the SCP perform a match for the Matching Keys and return values for the Return Keys that have been specified in the Identifier of the request, against the information that the SCP possesses, to the objects specified in the SOP Class. + + In this Annex, the term "Identifier" refers to the Identifier service parameter of the C-FIND service as defined in . + + + + The SCP generates a C-FIND response for each match with an Identifier containing the values of all Matching Key Attributes and all known Return Key Attributes requested. Each response contains one worklist item. All such responses will contain a status of Pending. A status of Pending indicates that the process of matching is not complete. + + + When the process of matching is complete a C-FIND response is sent with a status of Success and no Identifier. + + + A Refused or Failed response to a C-FIND request indicates that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-CANCEL-FIND request at any time during the processing of the C-FIND service. The SCP will interrupt all matching and return a status of Canceled. + + The SCU needs to be prepared to receive C-FIND responses sent by the SCP until the SCP finally processed the C-CANCEL-FIND request. + + + +
+
+
+ Worklist Information Model Definition + The Worklist Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + Information Model Definitions for standard SOP Classes of the Worklist Service Class are defined in this Annex. A Worklist Information Model Definition contains: + + + an Entity-Relationship Model Definition + + + a Key Attributes Definition; + + +
+ Entity-Relationship Model Definition + Basic Worklist Information Models consist of a single level, that includes all Matching Key Attributes and all Return Key Attributes, which may be sent from the SCU to the SCP in the request and whose values are expected to be returned from the SCP to the SCU in each of the responses (or worklist items). The Matching Key Attribute values in the request specify the worklist items that are to be returned in the responses. All Key Attributes (the Matching Key Attributes and the Return Key Attributes) in the request determine which Attribute values are returned in the responses for that worklist. + A Worklist Item has a one-to-one relationship with the real-world object defining the root for the Basic Worklist Information Model. In addition the worklist item is related to a number of other objects from the real-world model. Each of these real-world objects is represented by a hierarchy of entities organized in an (internal) Entity-Relationship Model. +
+
+ Attributes Definition + Attributes are defined for each entity in the internal Entity-Relationship Model. An Identifier in a C-FIND request shall contain values to be matched against the Attributes of the Entities in a Worklist Information Model. For any worklist request, the set of entities for which Attributes are returned, shall be determined by the set of Matching and Return Key Attributes specified in the Identifier. +
+ Attribute Types + All Attributes of entities in a Worklist Information Model shall be specified both as a Matching Key Attribute (either required or optional) and as a Return Key Attribute. +
+ Matching Key Attributes + The Matching Key Attributes are Keys, which select worklist items to be included in a requested Worklist. +
+ Required Matching Key Attributes + A Basic Worklist Management SCP shall support matching based on values of all Required Matching Key Attributes of the C-FIND request. Multiple entities may match a given value for a Required Key. + If an SCP manages an entity with an unknown Attribute value (i.e., zero length), the unknown value shall fail to match any Matching Key value. + + + + Even though there is no means to perform matching on such entities, they may be queried as a Return Key Attribute using a C-FIND request with a zero length value (universal match) or by a single wild card (wild card match). + + + An SCU may choose to supply any subset of Required Matching Key Attributes. + + + +
+
+ Optional Matching Key Attributes + In the Worklist Information Model, a set of Attributes may be defined as Optional Matching Key Attributes. Optional Matching Key Attributes contained in the Identifier of a C-FIND request may induce two different types of behavior depending on support for matching by the SCP. If the SCP + + + does not support matching on the Optional Matching Key Attribute, then the Optional Matching Key Attribute shall be ignored for matching but shall be processed in the same manner as a Return Key Attribute. + + + supports matching of the Optional Matching Key Attribute, then the Optional Matching Key Attribute shall be processed in the same manner as a Required Matching Key. + + + + + + The Conformance Statement of the SCP lists the Optional Matching Key Attributes that are supported for matching. + + + An SCU can not expect the SCP to support a match on an Optional Matching Key. + + + +
+
+
+ Return Key Attributes + The values of Return Key Attributes to be retrieved with the Worklist are specified with zero-length (universal matching) in the C-FIND request. SCPs shall support Return Key Attributes defined by a Worklist Information Model according to the Data Element Type (1, 1C, 2, 2C, 3) as defined in . + Every Matching Key Attribute shall also be considered as a Return Key Attribute. Therefore the C-FIND response shall contain in addition to the values of the requested Return Key Attributes the values of the requested Matching Key Attributes. + + + + The Conformance Statement of the SCP lists the Return Key Attributes of Type 3, which are supported. + + + An SCU may choose to supply any subset of Return Key Attributes. + + + An SCU can not expect to receive any Type 3 Return Key Attributes. + + + Return Key Attributes with VR of SQ may be specified either with zero-length or with the zero-length item in the sequence. + + + +
+
+
+ Attribute Matching + The following types of matching, which are defined by the Query/Retrieve Service Class in may be performed on Matching Key Attributes in the Basic Worklist Service Class. Different Matching Key Attributes may be subject for different matching types. The Worklist Information Model defines the type of matching for each Required Matching Key Attribute. The Conformance Statement of the SCP shall define the type of matching for each Optional Matching Key Attribute. The types of matching are: + + + Single Value Matching + + + List of UID Matching + + + Wild Card Matching + + + Range Matching + + + Sequence Matching + + + The following type of matching, which is defined by the Query/Retrieve Service Class in of this Part shall be performed on Return Key Attributes in the Basic Worklist Service Class. + + + Universal Matching + + + See and subsections for specific rules governing of Matching Key Attribute encoding for and performing of different types of matching. + The Specific Character Set (0008,0005) Attribute and/or the Timezone Offset From UTC (0008,0201) Attribute may be present in the Identifier but are never matched, i.e., they are not considered Matching Key Attributes. See for details. + Single value matching of Attributes with Person Name Value Representation may be affected by extended negotiation of fuzzy semantic matching of person names. +
+
+ Matching Multiple Values + When matching an Attribute that has a value multiplicity of greater than one, if any of the values match, then all values shall be returned. +
+
+
+
+ Worklist Information Model + Each Worklist Information Model is associated with one SOP Class. The following Worklist Information Model is defined: + + + Modality Worklist Information Model + + +
+
+ DIMSE-C Service Group + One DIMSE-C Service is used in the construction of SOP Classes of the Basic Worklist Management Service Class. The following DIMSE-C operation is used. + + + C-FIND + + +
+ C-FIND Operation + SCPs of some SOP Classes of the Basic Worklist Management Service Class are capable of processing queries using the C-FIND operation as described in . The C-FIND operation is the mechanism by which queries are performed. Matches against the keys present in the Identifier are returned in C-FIND responses. +
+ C-FIND Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Worklist Information Model against which the C-FIND is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-FIND operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-FIND operation with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. +
+
+ Identifier + Both the C-FIND request and response contain an Identifier encoded as a Data Set (see ). +
+ Request Identifier Structure + An Identifier in a C-FIND request shall contain + + + Key Attributes values to be matched against the values of Attributes specified in that SOP Class. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Request Identifier. It shall not be included otherwise. + + This means that Specific Character Set (0008,0005) is included if the SCU supports expanded or replacement character sets in the context of this service. It will not be included if expanded or replacement character sets are not supported by the SCU. + + + + Conditionally, the Attribute Timezone Offset From UTC (0008,0201). This Attribute shall be included if Key Attributes of time are to be interpreted explicitly in the designated local time zone. It shall not be present otherwise, i.e., it shall not be sent with a zero-length value. + + + The Key Attributes and values allowable for the query shall be defined in the SOP Class definition for the corresponding Worklist Information Model. +
+
+ Response Identifier Structure + The C-FIND response shall not contain Attributes that were not in the request or specified in this section. + An Identifier in a C-FIND response shall contain: + + + Key Attributes with values corresponding to Key Attributes contained in the Identifier of the request (Key Attributes as defined in .) + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Response Identifier. It shall not be included otherwise. The C-FIND SCP is not required to return responses in the Specific Character Set requested by the SCU if that character set is not supported by the SCP. The SCP may return responses with a different Specific Character Set. + + This means that Specific Character Set (0008,0005) is included if the SCP supports expanded or replacement character sets in the context of this service. It will not be included if expanded or replacement character sets are not supported by the SCP. + + + + Conditionally, the Attribute Timezone Offset From UTC (0008,0201). This Attribute shall be included if any Attributes of time in the Response Identifier are to be interpreted explicitly in the designated local time zone. It shall not be present otherwise, i.e., it shall not be sent with a zero-length value. + + + Conditionally, the Attribute HL7 Structured Document Reference Sequence (0040,A390) and its subsidiary Sequence Items. This Attribute shall be included if HL7 Structured Documents are referenced within the Identifier, e.g., in the Pertinent Documents Sequence (0038,0100). + + +
+
+
+ Status + + defines the status code values that might be returned in a C-FIND response. Fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-FIND Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources + + A700 + + (0000,0902) +
+ + Identifier Does Not Match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ + Unable to process + + Cxxx + + (0000,0901) + (0000,0902) +
+ Cancel + + Matching terminated due to Cancel request + + FE00 + + None +
+ Success + + Matching is complete - No final Identifier is supplied. + + 0000 + + None +
+ Pending + + Matches are continuing - Current Match is supplied and any Optional Keys were supported in the same manner as Required Keys. + + FF00 + + Identifier +
+ + Matches are continuing - Warning that one or more Optional Keys were not supported for existence for this Identifier. + + FF01 + + Identifier +
+ + Status Codes are returned in DIMSE response messages (see ). The code values stated in column "Status Codes" are returned in Status Command Element (0000,0900). + +
+
+
+ C-FIND SCU Behavior + All C-FIND SCUs shall be capable of generating query requests that meet the requirements of the "Worklist" Search Method (see ). + Required Keys, and Optional Keys associated with the Worklist may be contained in the Identifier. + An SCU conveys the following semantics using the C-FIND requests and responses: + + + The SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information it possesses of the Worklist specified in the request. + + + The SCU shall interpret Pending responses to convey the Attributes of a match of an Entity. + + + The SCU shall interpret a response with a status equal to Success, Failed, Refused or Cancel to convey the end of Pending responses. + + + The SCU shall interpret a Refused or Failed response to a C-FIND request as an indication that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-FIND-CANCEL request at any time during the processing of the C-FIND. The SCU shall recognize a status of Cancel to indicate that the C-FIND-CANCEL was successful. + + +
+
+ C-FIND SCP Behavior + All C-FIND SCPs shall be capable of processing queries that meet the requirements of the "Worklist" Search (see ). + An SCP conveys the following semantics using the C-FIND requests and responses: + + + The SCP is requested to perform a match of all the keys specified in the Identifier of the request, against the information it possesses. Attribute matching is performed using the key values specified in the Identifier of the C-FIND request as defined in . + + + The SCP generates a C-FIND response for each match using the "Worklist" Search method. All such responses shall contain an Identifier whose Attributes contain values from a single match. All such responses shall contain a status of Pending. + + + When all matches have been sent, the SCP generates a C-FIND response that contains a status of Success. A status of Success shall indicate that a response has been sent for each match known to the SCP. + + + + No ID is contained in a response with a status of Success. For a complete definition, see . + + + When there are no matches, then no responses with a status of Pending are sent, only a single response with a status of Success. + + + + + + The SCP shall generate a response with a status of Refused or Failed if it is unable to process the request. A Refused or Failed response shall contain no Identifier. + + + If the SCP receives C-FIND-CANCEL indication before it has completed the processing of the matches it shall interrupt the matching process and return a status of Cancel. + + +
+ "Worklist" Search Method + The following procedure is used to generate matches. + The key match strings contained in the Identifier of the C-FIND request are matched against the values of the Key Attributes for each worklist entity. For each entity for which the Attributes match all of the specified match strings, construct an Identifier. This Identifier shall contain all of the values of the Attributes for this entity that match those in the C-FIND request. Return a response for each such Identifier. If there are no matching keys, then there are no matches, return a response with a status equal to Success and with no Identifier. +
+
+
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation procedure specified in shall be used to negotiate the supported SOP Classes or Meta SOP Classes. + Support for the SCP/SCU role selection negotiation is optional. The SOP Class Extended Negotiation is optional. +
+ SOP Class Extended Negotiation + The SOP Class Extended Negotiation allows, at Association establishment, peer DICOM AEs to exchange application Association information defined by specific SOP Classes. This is achieved by defining the Service-class-application-information field. The Service-class-application-information field is used to define support for fuzzy semantic matching of person names. + This negotiation is optional. If absent, the default conditions shall be: + + + literal matching of person names with case sensitivity unspecified + + + timezone query adjustment unspecified + + + The Association-requester, for each SOP Class, may use one SOP Class Extended Negotiation Sub-Item. The SOP Class is identified by the corresponding Abstract Syntax Name (as defined by ) followed by the Service-class-application-information field. This field defines three or more sub-fields: + + + reserved; shall always be 1 + + + reserved; shall always be 1 + + + literal or fuzzy semantic matching of person names by the Association-requester + + + timezone query adjustment by the Association-requester + + + The meaning of fuzzy semantic person name matching and of timezone query adjustment is as defined in and . + The Association-acceptor shall return a three byte field (three sub-fields) if offered a three byte field (three sub-fields) by the Association-requester. The Association-acceptor may return more than three bytes if offered more than three bytes by the Association-requester. A three byte response to a more than three byte request means that the missing sub-field shall be treated as 0 values. + The Association-acceptor, for each sub-field of the SOP Class Extended Negotiation Sub-Item offered, either accepts the Association-requester proposal by returning the same value (1) or turns down the proposal by returning the value (0).. + If the SOP Class Extended Negotiation Sub-Item is not returned by the Association-acceptor then fuzzy semantic matching of person names is not supported and timezone query adjustment is unspecified over the Association (default condition). + If the SOP Class Extended Negotiation Sub-Items do not exist in the A-ASSOCIATE indication they shall be omitted in the A-ASSOCIATE response. +
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-RQ) + The SOP Class Extended Negotiation Sub-Item consists of a sequence of mandatory fields as defined by . This field shall be three or four bytes in length. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-RQ
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + reserved + + This byte field shall always be 1 +
+ 2 + + reserved + + This byte field shall always be 1 +
+ 3 + + Fuzzy semantic matching of person names + + This byte field defines whether or not fuzzy semantic person name Attribute is requested by the Association-requester. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - fuzzy semantic matching not requested + 1 - fuzzy semantic matching requested +
+ 4 + + Timezone query adjustment + + This byte field defines whether or not the Attribute Timezone Offset From UTC (0008,0201) shall be used to adjust the query meaning for time and datetime fields in queries. + 0 - Timezone query adjustment not requested + 1 - Timezone query adjustment requested +
+ + This Sub-Item is identical to Extended Negotiation Sub-Items as used by the Query/Retrieve SOP Classes. However, relational queries (Byte 1) are not relevant since the worklist information models are single level, and date-time matching (Byte 2) is already required by the worklist information models and Enhanced Multi-Frame Image Conversion support is not applicable (Byte 5). + +
+
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-AC) + The SOP Class Extended Negotiation Sub-Item is made of a sequence of mandatory fields as defined by . This field shall be three or four bytes in length. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-AC
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + reserved + + This byte field shall always be 1 +
+ 2 + + reserved + + This byte field shall always be 1 +
+ 3 + + Fuzzy semantic matching of person names + + This byte field defines whether or not fuzzy semantic person name Attribute matching will be performed by the Association-acceptor. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - fuzzy semantic matching not performed + 1 - fuzzy semantic matching performed +
+ 4 + + Timezone query adjustment + + This byte field defines whether or not the Attribute Timezone Offset From UTC (0008,0201) shall be used to adjust the query meaning for time and datetime fields in queries. + 0 - Timezone adjustment of queries not performed + 1 - Timezone adjustment of queries performed +
+
+
+
+
+ SOP Class Definitions +
+ Modality Worklist SOP Class +
+ Modality Worklist SOP Class Overview + The Modality Worklist SOP class defined within the Basic Worklist Management Service Class defines an application-level class of service that facilitates the communication of information to the imaging modality about Scheduled Procedure Steps, and entities related to the Scheduled Procedure Steps. As will be detailed below, part of the information carried by the worklist mechanism is intended to be used by the imaging modality itself, but much of the information is intended to be presented to the modality operator. + This worklist is structured according to Scheduled Procedure Steps. A procedure step is a unit of service in the context of a requested imaging procedure. + The Modality Worklist SOP class supports the following requirements: + + + Verify patient (e.g., download patient demographic information from IS to Modality, to verify that the person to be examined is the intended subject). + + + Select a Scheduled Procedure Step from the IS (e.g., download procedure step information from the IS to the Modality). The Modality Worklist SOP Class supports two alternatives for the realization of this requirement, supporting different organization methods of the department: + + + The Modality may obtain the list of Scheduled Procedure Steps from the IS. Display of the list and selection from the list is done at the Modality. + + + The list is displayed and selection is performed on the IS. This implies, that the information is obtained by the Modality just before the Scheduled Procedure Step starts. + + + + + Prepare the performance of a Scheduled Procedure Step. + + + Couple DICOM images unambiguously with related information from the IS (e.g., patient demographics, procedure description, ID data structure from the IS, contextual IS information). + + + Capture all the Attributes from the IS, that are mandatory to be inserted into the DICOM Image Object + + + The Modality Worklist SOP Class is not intended to provide access to all IS information and services that may be of interest to a Modality operator or attending physician. Its primary focus is the efficient operation of the image acquisition equipment. DICOM SOP Classes such as the Relevant Patient Information Query SOP Class and non-DICOM Services that fall beyond the scope of the Modality Worklist SOP Class may be needed. + The Modality Worklist SOP Class does not support the transmission of information from the Modality to the information system. +
+
+ Modality Worklist Information Model +
+ E/R Model + In response to a given C-FIND request, the SCP might have to send several C-FIND responses, (i.e., one C-FIND response per matching worklist item). Each worklist item focuses on one Scheduled Procedure Step and the related information. The E-R diagram presented in depicts the content of one C-FIND request, that is: + + + the matching Scheduled Procedure Step, the Requested Procedure to which the Scheduled Procedure Step contributes, the Imaging Service Request in which the associated Requested Procedure is ordered, any associated Visit, and the Patient who is to be the subject of the Procedure. + + + Therefore, for a given C-FIND request, a given Scheduled Procedure Step will appear in only one of the resulting C-FIND responses. Obviously, information about the Requested Procedure, Imaging Service Request, Visit and Patient may be mentioned in several of these C-FIND responses. + The Modality Worklist Information Model is represented by the Entity Relationship diagram shown in figure -1. + + The entities appearing in messages related to the Modality Worklist SOP Class are required to comply to the Modality Worklist model. However, DICOM does not define the internal structure of the database. + + The entry point of the Modality Worklist is the Scheduled Procedure Step entity. + The Attributes of a Scheduled Procedure Step Worklist can be found in the following Modules in . + + + Patient Relationship Module + + + Patient Identification Module + + + Patient Demographic Module + + + Patient Medical Module + + + Visit Relationship Module + + + Visit Identification Module + + + Visit Status Module + + + Visit Admission Module + + + Scheduled Procedure Step Module + + + Requested Procedure Module + + + Imaging Service Request Module + + + +
+ Modality Worklist Information Model E/R Diagram + + + + + + +
+
+
+
+ Modality Worklist Attributes + + defines the Attributes of the Modality Worklist Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Modality Worklist Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + Scheduled Procedure Step + +
+ Scheduled Procedure Step Sequence + + (0040,0100) + + R + + 1 + + The Attributes of the Scheduled Procedure Step shall only be retrieved with Sequence Matching. + The Scheduled Procedure Step Sequence shall contain only a single Item. +
+ >Scheduled Station AE Title + + (0040,0001) + + R + + 1 + + Scheduled Station AE Title shall be retrieved with Single Value Matching only. +
+ >Scheduled Procedure Step Start Date + + (0040,0002) + + R + + 1 + + Scheduled Step Start Date shall be retrieved with Single Value Matching or Range Matching. + See remark under Scheduled Procedure Step Start Time (0040,0003). +
+ >Scheduled Procedure Step Start Time + + (0040,0003) + + R + + 1 + + Scheduled Step Start Time shall be retrieved with Single Value Matching or Range Matching. Scheduled Step Start Date and Scheduled Step Start Time are subject to Range Matching. If both keys are specified for Range Matching, e.g., the date range July 5 to July 7 and the time range 10am to 6pm specifies the time period starting on July 5, 10am until July 7, 6pm. + + If the Information System does not provide scheduling for individual Procedure Steps, it may use the closest scheduling information it possesses (e.g., Procedures are subject to scheduling instead of Procedure Steps). + +
+ >Modality + + (0008,0060) + + R + + 1 + + The Modality shall be retrieved with Single Value Matching. +
+ >Scheduled Performing Physician's Name + + (0040,0006) + + R + + 2 + + Scheduled Performing Physician's Name shall be retrieved with Single Value Matching or Wild Card Matching. +
+ >Scheduled Procedure Step Description + + (0040,0007) + + O + + 1C + + Either the Scheduled Procedure Step Description (0040,0007) or the Scheduled Protocol Code Sequence (0040,0008) or both shall be supported by the SCP. +
+ >Scheduled Station Name + + (0040,0010) + + O + + 2 + +
+ >Scheduled Procedure Step Location + + (0040,0011) + + O + + 2 + +
+ >Scheduled Protocol Code Sequence + + (0040,0008) + + O + + 1C + + Either the Scheduled Procedure Step Description (0040,0007) or the Scheduled Protocol Code Sequence (0040,0008) or both shall be supported by the SCP. + The Scheduled Protocol Code Sequence contains one or more Items. +
+ >>Code Value + + (0008,0100) + + O + + 1 + +
+ >>Coding Scheme Version + + (0008,0103) + + O + + 3 + +
+ >>Coding Scheme Designator + + (0008,0102) + + O + + 1 + +
+ >>Code Meaning + + (0008,0104) + + O + + 3 + +
+ >>Protocol Context Sequence + + (0040,0440) + + - + + 3 + + The Protocol Context Sequence and its Items shall not be used for matching +
+ >>>Value Type + + (0040,A040) + + - + + 1 + +
+ >>>Concept Name Code Sequence + + (0040,A043) + + - + + 1 + +
+ >>>>Code Value + + (0008,0100) + + - + + 1 + +
+ >>>>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>>>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>>>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >>>DateTime + + (0040,A120) + + - + + 1C + + Required if Value Type (0040,A040) is DATETIME. +
+ >>>Person Name + + (0040,A123) + + - + + 1C + + Required if Value Type (0040,A040) is PNAME. +
+ >>>Text Value + + (0040,A160) + + - + + 1C + + Required if Value Type (0040,A040) is TEXT. +
+ >>>Concept Code Sequence + + (0040,A168) + + - + + 1C + + Required if Value Type (0040,A040) is CODE. +
+ >>>>Code Value + + (0008,0100) + + - + + 1 + +
+ >>>>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>>>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>>>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >>>Numeric Value + + (0040,A30A) + + - + + 1C + + Required if Value Type (0040,A040) is NUMERIC. +
+ >>>Measurement Units Code Sequence + + (0040,08EA) + + - + + 1C + + Required if Value Type (0040,A040) is NUMERIC. +
+ >>>>Code Value + + (0008,0100) + + - + + 1 + +
+ >>>>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>>>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>>>Code Meaning + + (0008,0104) + + - + + 1 + +
+ + >>>All other Attributes of the Protocol Context Sequence + + + + - + + 3 + +
+ >Pre-Medication + + (0040,0012) + + O + + 2C + + Required if Pre-Medication is to be applied to that Scheduled Procedure Step. +
+ >Scheduled Procedure Step ID + + (0040,0009) + + O + + 1 + +
+ >Requested Contrast Agent + + (0032,1070) + + O + + 2C + + Required if Contrast Media is to be applied to that Scheduled Procedure Step. +
+ >Scheduled Procedure Step Status + + (0040,0020) + + O + + 3 + +
+ + >All other Attributes of the Scheduled Procedure Step Sequence + + + + O + + 3 + +
+ Scheduled Specimen Sequence + + (0040,0500) + + O + + 3 + + One or more Items may be returned in this Sequence. +
+ >Container Identifier + + (0040,0512) + + O + + 1 + +
+ >Container Type Code Sequence + + (0040,0518) + + - + + 2 + + Zero or one Item shall be returned in this Sequence. +
+ >>Code Value + + (0008,0100) + + - + + 1 + +
+ >>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >Specimen Description Sequence + + (0040,0560) + + O + + 1 + + One or more Items shall be returned in this Sequence. +
+ >>Specimen Identifier + + (0040,0551) + + O + + 1 + +
+ >>Specimen UID + + (0040,0554) + + O + + 1 + +
+ + >>All other Attributes of the Specimen Description Sequence + + + + O + + 3 + + Specimen Preparation Sequence (0040,0610), if present, describes preparation steps already performed, not scheduled procedure steps +
+ + >All other Attributes of the Scheduled Specimen Sequence + + + + O + + 3 + +
+ + Requested Procedure + +
+ Requested Procedure ID + + (0040,1001) + + O + + 1 + +
+ Requested Procedure Description + + (0032,1060) + + O + + 1C + + The Requested Procedure Description (0032,1060) or the Requested Procedure Code Sequence (0032,1064) or both shall be supported by the SCP. +
+ Requested Procedure Code Sequence + + (0032,1064) + + O + + 1C + + The Requested Procedure Description (0032,1060) or the Requested Procedure Code Sequence (0032,1064) or both shall be supported by the SCP. + The Requested Procedure Code Sequence shall contain only a single Item. +
+ >Code Value + + (0008,0100) + + O + + 1 + +
+ >Coding Scheme Designator + + (0008,0102) + + O + + 1 + +
+ >Coding Scheme Version + + (0008,0103) + + O + + 3 + +
+ >Code Meaning + + (0008,0104) + + O + + 3 + +
+ Study Instance UID + + (0020,000D) + + O + + 1 + +
+ Study Date + + (0008,0020) + + O + + 3 + + See note 5. +
+ Study Time + + (0008,0030) + + O + + 3 + + See note 5. +
+ Referenced Study Sequence + + (0008,1110) + + O + + 2 + +
+ >Referenced SOP Class UID + + (0008,1150) + + O + + 1 + +
+ >Referenced SOP Instance UID + + (0008,1155) + + O + + 1 + +
+ Requested Procedure Priority + + (0040,1003) + + O + + 2 + +
+ Patient Transport Arrangements + + (0040,1004) + + O + + 2 + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + Imaging Service Request + +
+ Accession Number + + (0008,0050) + + O + + 2 + +
+ Issuer of Accession Number Sequence + + (0008,0051) + + O + + 3 + +
+ Requesting Physician + + (0032,1032) + + O + + 2 + +
+ Referring Physician's Name + + (0008,0090) + + O + + 2 + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + Visit Identification + +
+ Admission ID + + (0038,0010) + + O + + 2 + +
+ Issuer of Admission ID Sequence + + (0038,0014) + + O + + 3 + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + Visit Status + +
+ Current Patient Location + + (0038,0300) + + O + + 2 + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + Visit Relationship + +
+ Referenced Patient Sequence + + (0008,1120) + + O + + 2 + +
+ >Referenced SOP Class UID + + (0008,1150) + + O + + 1 + +
+ >Referenced SOP Instance UID + + (0008,1155) + + O + + 1 + +
+ + All other Attributes of the except those explicitly included in this table (see Note 3) + + + + O + + 3 + +
+ + Visit Admission + +
+ All Attributes from the Visit Admission Module + + + O + + 3 + +
+ + Patient Relationship + +
+ All Attributes from the Patient Relationship Module except those explicitly included in this table (see Note 3) + + + O + + 3 + +
+ + Patient Identification + +
+ Patient's Name + + (0010,0010) + + R + + 1 + + Patient Name shall be retrieved with Single Value Matching or Wild Card Matching. +
+ Patient ID + + (0010,0020) + + R + + 1 + + Patient ID shall be retrieved with Single Value Matching. +
+ Issuer of Patient ID + + (0010,0021) + + O + + 3 + + +
+ Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + O + + 3 + + +
+ Other Patient IDs Sequence + + (0010,1002) + + O + + 3 + + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + Patient Demographic + +
+ Patient's Birth Date + + (0010,0030) + + O + + 2 + +
+ Patient's Sex + + (0010,0040) + + O + + 2 + +
+ Patient's Primary Language Code Sequence + + (0010,0101) + + O + + 3 + + The languages that can be used to communicate with the patient. + If returned, the Patient's Primary Language Code Sequence shall contain one or more Items. The items are ordered by preference (most preferred language to least preferred language). +
+ >Code Value + + (0008,0100) + + O + + 1 + +
+ >Coding Scheme Designator + + (0008,0102) + + O + + 1 + +
+ >Code Meaning + + (0008,0104) + + - + + 1 + + Code Meaning shall not be used as Matching Key. +
+ >Patient's Primary Language Modifier Code Sequence + + (0010,0102) + + O + + 3 + + A modifier for a Patient's Primary Language. Can be used to specify a national language variant. + If returned, the Patient's Primary Language Modifier Code Sequence shall contain only a single Item. +
+ >>Code Value + + (0008,0100) + + O + + 1 + +
+ >>Coding Scheme Designator + + (0008,0102) + + O + + 1 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + + Code Meaning shall not be used as Matching Key. +
+ Patient's Weight + + (0010,1030) + + O + + 2 + +
+ Patient's Size + + (0010,1020) + + O + + 3 + +
+ Confidentiality constraint on patient data + + (0040,3001) + + O + + 2 + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + Patient Medical + +
+ Patient State + + (0038,0500) + + O + + 2 + +
+ Pregnancy Status + + (0010,21C0) + + O + + 2 + +
+ Medical Alerts + + (0010,2000) + + O + + 2 + +
+ Allergies + + (0010,2110) + + O + + 2 + +
+ Special Needs + + (0038,0050) + + O + + 2 + +
+ Pertinent Documents Sequence + + (0038,0100) + + O + + 3 + + Pertinent Documents Sequence shall be retrieved with Universal Matching only +
+ >Referenced SOP Class UID + + (0008,1150) + + - + + 1 + +
+ >Referenced SOP Instance UID + + (0008,1155) + + - + + 1 + +
+ >Purpose of Reference Code Sequence + + (0040,A170) + + - + + 2 + +
+ >>Code Value + + (0008,0100) + + - + + 1 + +
+ >>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >Document Title + + (0042,0010) + + - + + 2 + +
+ + All other Attributes of the + + + + + O + + 3 + +
+ + + + Just like Series and Image Entities specified in the Query/Retrieve Service Class either an SCU or an SCP may support optional Matching Key Attributes and/or Type 3 Return Key Attributes that are not included in the Worklist Information Model (i.e., standard or private Attributes). This is considered a Standard Extended SOP Class (see ). + + + Each Module contains a Comment Attribute. This may be used to transmit non-structured information, which may be displayed to the operator of the Modality. + + + The reason for this exclusion is to assure that the Attributes that may be present in multiple Modules are included only once with the meaning pertaining to only one Module (for example, Referenced Study Sequence (0008,1110) shall be included once with the meaning as defined in the Requested Procedure Module). + + + The use of Specific Character Set is discussed in section and . + + + The values of Study Date (0008,0020) and Study Time (0008,0030) may be provided in order to achieve consistency of Study level Attributes in composite instances generated in multiple performed procedure steps on different devices, and the worklist values may be updated by the SCP based on information received from Modality Performed Procedure Steps or by examining the composite instances generated. + + + + The Attributes in are not part of the Worklist Information Model; their inclusion in the C-FIND request and response identifier are governed by rules in sections and , respectively. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Modality Worklist C-FIND Identifier
+ + Attribute Name + + + + Tag + + + + Request Identifier + + + + Response Identifier + + + + Remark Type + +
+ Specific Character Set + + (0008,0005) + + 1C + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and + +
+ Timezone Offset From UTC + + (0008,0201) + + 1C + + 1C + + This Attribute is required if times are to be interpreted explicitly in the designated local timezone. See and + +
+ HL7 Structured Document Reference Sequence + + (0040,A390) + + - + + 1C + + One or more Items may be included in this sequence. + Required if HL7 Structured Documents are referenced within the Identifier. See + +
+ >Referenced SOP Class UID + + (0008,1150) + + - + + 1 + +
+ >Referenced SOP Instance UID + + (0008,1155) + + - + + 1 + +
+ >HL7 Instance Identifier + + (0040,E001) + + - + + 1 + +
+ >Retrieve URI + + (0040,E010) + + - + + 3 + +
+
+
+
+ Conformance Requirements + An implementation may conform to the Modality Worklist SOP Class as an SCU or an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that conforms to the Modality Worklist SOP Class shall support queries against the Worklist Information Model described in of this Annex using the baseline C-FIND SCU Behavior described in of this Part. + An implementation that conforms to the Modality Worklist SOP Class as an SCU shall state in its Conformance Statement whether it requests matching on Optional Matching Key Attributes. If it requests Type 3 Return Key Attributes, then it shall list these Optional Return Key Attributes. It shall identify any Templates it supports for the Protocol Context Sequence. + An implementation that conforms to the Modality Worklist SOP Class as an SCU shall state in its Conformance Statement whether or not it supports extended negotiation of fuzzy semantic matching of person names. + An implementation that conforms to the Modality Worklist SOP Class as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when encoding queries and interpreting responses. +
+
+ SCP Conformance + An implementation that conforms to the Modality Worklist SOP Class shall support queries against the Worklist Information Model described in of this Annex using the C-FIND SCP Behavior described in of this Part. + An implementation that conforms to the Modality Worklist SOP Class as an SCP shall state in its Conformance Statement whether it supports matching on Optional Matching Key Attributes. If it supports Type 3 Return Key Attributes, then it shall list the Optional Return Key Attributes that it supports. It shall identify any Templates it supports for the Protocol Context Sequence. + An implementation that conforms to the Modality Worklist SOP Class as an SCP shall state in its Conformance Statement whether it supports case-insensitive matching for PN VR Attributes and list Attributes for which this applies. + An implementation that conforms to the Modality Worklist SOP Class as an SCP shall state in its Conformance Statement whether or not it supports extended negotiation of fuzzy semantic matching of person names. If fuzzy semantic matching of person names is supported, then the mechanism for fuzzy semantic matching shall be specified. + An implementation that conforms to the Modality Worklist SOP Class as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when interpreting queries, performing matching and encoding responses. +
+
+
+ SOP Class + The Modality Worklist SOP Class in the Basic Worklist Service Class identifies the Modality Worklist Information Model, and the DIMSE-C operations supported. The following Standard SOP Class is identified: + + + + + + + + + + + + + + +
Modality Worklist SOP Class
+ + SOP Class Name + + + + SOP Class UID + +
+ Modality Worklist Information Model - FIND + + 1.2.840.10008.5.1.4.31 +
+
+
+
+ General Purpose Worklist SOP Class (Retired) + Retired. See PS 3.4-2011. +
+
+
+ Examples for the Usage of the Modality Worklist (Informative) + Moved to + +
+
+ General Purpose Worklist Example (Informative) (Retired) + Retired. See PS 3.17-2011. +
+
+ + Queue Management Service Class (Normative) + Retired. See PS 3.4-2004. + + + Handling of Identifying Parameters (Informative) + Retired. See . + + + Softcopy Presentation State Storage SOP Classes (Normative) +
+ Overview +
+ Scope + The Softcopy Presentation State Storage SOP Classes extend the functionality of the Storage Service class (defined in ) to add the ability to convey an intended presentation state or record an existing presentation state. The SOP Classes specify the information and behavior that may be used to present (display) images that are referenced from within the SOP Classes. + They include capabilities for specifying: + + + the output grayscale space in P-Values + + + the color output space as PCS-Values + + + grayscale contrast transformations including modality, VOI and presentation LUT + + + mask subtraction for multi-frame grayscale images + + + selection of the area of the image to display and whether to rotate or flip it + + + image and display relative annotations, including graphics, text and overlays + + + the blending of two image sets into a single presentation + + + The grayscale softcopy presentation state refers to the grayscale image transformations that are to be applied in an explicitly defined manner to convert the stored image pixel data values in a Composite Image Instance to presentation values (P-Values) when an image is displayed on a softcopy device. The P-Values are in a device independent perceptually linear space that is formally defined in . + The color and pseudo-color softcopy presentation states refer to the color image transformations that are to be applied in an explicitly defined manner to convert the stored image pixel data values in a Composite Image Instance to Profile Connection Space values (PCS-Values) when an image is displayed on a softcopy device. The PCS-Values are in a device independent space that is formally defined in the ICC Profiles as CIEXYZ or CIELab values. + The blending presentation states specify two sets of images, an underlying set, and a superimposed set, and the manner in which their pixel values are blended. The underlying set is rendered as grayscale and the superimposed set is rendered as color. The blending is not defined in a pair wise image-by-image or frame-by-frame manner, but rather the manner in which the two sets are combined is left to the discretion of the implementation. Specifically, matters of spatial registration, and any re-sampling and the mechanism of interpolation are not specified. + The Softcopy Presentation State Storage SOP Classes may be used to store a single state per image, or a common state to be shared by multiple selected images. All images to which the Grayscale, Color and Pseudo-Color Presentation States apply must be a part of the same study that the stored state is a part of, and be of a single Composite Image Storage SOP Class. + The two sets of images to which the Blended Presentation State applies may be in separate Studies, Each set shall be within a single study. Each set shall be of a single Composite Image Storage SOP Class. + How an SCU of this SOP Class records or generates this state is beyond the scope of the standard. + + For example, an acquisition device may acquire, reconstruct and store to a workstation or archive images that are later examined by an operator for the purpose of quality assurance or printing. At that time a selected grayscale transformation (such as a window level/width operation) may be applied by the operator, and that activity captured and saved as a Grayscale Softcopy Presentation State Storage SOP Instance to the same workstation or archive, from which it is subsequently available for use by another user. Another workstation may retrieve the state for later use. Alternatively, an automated algorithm may derive a state from analysis of image statistics, body part examined, or other characteristics. + + How an SCP of this SOP Class chooses between multiple states that may apply to an image is beyond the scope of this standard, other than to state that a claim of conformance as an SCP of this SOP Class implies that the SCP shall make the presentation state available to the user of the device, and if selected by the user, shall apply all the transformations stored in the state in the manner in which they are defined in the standard. + + + + For example, an acquisition device may automatically store appropriate presentation states for series of images as they are reconstructed that represent adequate defaults. A user or algorithm may subsequently determine a more appropriate presentation state that more effectively displays the contents of an image, or record some annotation related directly to the image, and record that as another presentation state for an image. An application subsequently may display the image by automatically choosing to use the more recently saved or more specific presentation state, or may use the more general default presentation state for all images but notify the user that alternative presentation states are available. + + + Choice of the same presentation state to display a grayscale image on two devices claiming conformance to these SOP Classes implies through the definition of the P-Value space that the displayed image on both devices will be perceptually similar within the limits defined in , regardless of the actual capabilities of the display systems. + + + Choice of the same presentation state to display a color image on two devices claiming conformance to these SOP Classes implies through the definition of the PCS-Value space that the displayed image on both devices will appear similar in color regardless of the actual capabilities of the display systems. + + + DICOM color images without an embedded optional ICC profile have no defined color space, regardless of their representation. The implementation creating a Color Softcopy Presentation State with an ICC profile is explicitly defining a color space in which to interpret that image, even if one was not known at the time that the image was created. Often a well-known color space such as sRGB will be used in the presentation state under such circumstances. + + + +
+
+
+ Pixel Transformation Sequence + The Softcopy Presentation State Storage SOP Classes support a sequence of transformations that completely define the conversion of a stored image into a displayed image. + The sequence of transformations from stored pixel values into P-Values or PCS-Values is explicitly defined in a conceptual model. The actual sequence implemented may differ but must result in the same appearance. describes this sequence of transformations. + + + + Even though a Composite Image Storage SOP Class may not include some Modules that are part of the described transformations, the Softcopy Presentation State Storage SOP Classes do include them. For example, the CT Image Storage SOP Class includes Rescale Slope and Intercept in the CT Image Module, but does not include the Modality LUT Module, and hence is restricted to the description of linear transformations. A saved presentation state that refers to a CT Image Storage SOP Instance may include a Modality LUT, and hence may apply a non-linear transformation. + + + For the shutter, annotation and spatial transformations, the order in which they are applied relative to the other transformations should not result in a different appearance. The one exception is when a spatial transformation is applied that involves magnification implemented with interpolation. In this case, whether the interpolation is performed before or after the contrast transformations (such as VOI LUT) may result in a slightly different appearance. It is not considered necessary to constrain this sequence more precisely. + + + + The transformations defined in the Softcopy Presentation State Storage SOP Classes replace those that may be defined in the Referenced Image SOP Instance. If a particular transformation is absent in the Softcopy Presentation State Storage SOP Class, then it shall be assumed to be an identity transformation, and any equivalent transformation, if present, in the Referenced Image SOP Instance shall NOT be used instead. + Values of MONOCHROME1 and MONOCHROME2 for Photometric Interpretation (0028,0004) in the Referenced Image SOP Instance shall be ignored, since their effect is defined by the application of the grayscale presentation state transformations. + + These requirements are in order to achieve complete definition of the entire transformation in the Softcopy Presentation State Storage SOP Classes, and not to depend on the content of the Referenced Image SOP Instance, which may change. + + The Referenced Image Storage SOP Instance may also contain bit-mapped overlays. The Softcopy Presentation State Storage SOP Classes specify a mechanism for turning these on or off (i.e., displaying them or not). + The presentation related Attributes of the Softcopy Presentation State Storage SOP Classes are immutable. They shall never be modified or updated; only a derived SOP Instance with a new SOP Instance UID may be created to represent a different presentation. + When a Supplemental Palette Color LUT is present in a grayscale Referenced Image Storage SOP Instance: + + + The grayscale pipeline in any applicable Grayscale Softcopy Presentation State Storage SOP Instance or Blended Softcopy Presentation State Storage SOP Instance shall be applied only to the range of grayscale stored pixel values, and the presentation state shall not affect the rendering of the indexed color values. + + + + + A Color Softcopy Presentation State Storage SOP Instance shall not be applied. + + + A Pseudo-color Softcopy Presentation State Storage SOP Instance may be applied, in which case the Supplemental Palette Color LUT information shall be ignored. + + + No mechanism for separately specifying color consistency of the colors in the Supplemental Palette Color LUT is presently defined, only the optional inclusion of an ICC profile in the image instance. + + + +
+ Grayscale and Color Image Transformation Models + + + + + + +
+
+
+ Grayscale Transformations +
+ Modality LUT + The Modality LUT operation applies only to grayscale values. + The Modality LUT transformation transforms the manufacturer dependent pixel values into pixel values that are meaningful for the modality and are manufacturer independent (e.g., Hounsfield number for CT modalities, Optical Density for film digitizers). These may represent physical units or be dimensionless. The Modality LUT in the Presentation State is modality dependent and is analogous to the same Module in an Image. + + + + In some cases, such as the CT Image Storage SOP Class, the same conceptual step as the Modality LUT is specified in another form, for example as Rescale Slope and Rescale Intercept Attributes in the CT Image Module, though the Modality LUT Module is not part of the CT Image IOD. + + + Image pixel values with a value of Pixel Padding Value (0028,0120) in the referenced image, or within the range specified by Pixel Padding Value (0028,0120) and Pixel Padding Range Limit (0028,0121) (if present in the referenced image) shall be accounted for prior to entry to the Modality LUT stage. See the definition of Pixel Padding Value in . Neither Pixel Padding Value (0028,0120) nor Pixel Padding Range Limit (0028,0121) are encoded in the Presentation State Instance. + + + + In the case of a linear transformation, the Modality LUT is described by the Rescale Slope (0028,1053) and Rescale Intercept (0028,1052). In the case of a non-linear transformation, the Modality LUT is described by the Modality LUT Sequence. The rules for application of the Modality LUT are defined in . + If the Modality LUT or equivalent Attributes are part of both the Image and the Presentation State, then the Presentation State Modality LUT shall be used instead of the Image Modality LUT or equivalent Attributes in the Image. If the Modality LUT is not present in the Presentation State it shall be assumed to be an identity transformation. Any Modality LUT or equivalent Attributes in the Image shall not be used. +
+
+ Mask + The Mask operation applies only to grayscale values. + The mask transformation may be applied in the case of multi-frame images for which other frames at a fixed frame position or time interval relative to the current frame may be subtracted from the current frame. Multiple mask frames may be averaged, and sub-pixel shifted before subtraction. + This transformation uses the Mask Module as used in the X-Ray Angiography Image Storage SOP Class, though it may be applied to any Image Storage SOP Instance that contains a multi-frame image. + In the case of X-Ray images, the subtraction is specified to take place in a space logarithmic to X-Ray intensity. If the stored pixel values are not already in such a space, an implementation defined transformation to such a space must be performed prior to subtraction. If a Modality LUT Module is present as well as a Mask Module, then the Modality LUT shall specify a transformation into such a logarithmic space, otherwise it shall not be present (even though a Modality LUT may be present in the referenced image(s), which shall be ignored). + + + + In the case of an XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the image is LOG, then even though a Modality LUT would be present in the image (to map pixel values back to linear to X-Ray intensity), no Modality LUT would be present in the presentation state (i.e., the Modality LUT would be an identity transformation) since log values are required for subtraction. See . + + + In the case of an XA or XRF image, if the Pixel Intensity Relationship (0028,1040) is LIN, then no Modality LUT would be present in the image, but a Modality LUT would need to be present in the presentation state since log values are required for subtraction. + + + In the case of an XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the image is DISP, then even though a Modality LUT may or may not be present in the image (to map pixel values back to linear to X-Ray intensity), a different Modality LUT would be present in the presentation state if the creator of the presentation state could create a transformation from DISP pixel values to a logarithmic space for subtraction, or the Modality LUT in the presentation state would be an identity transformation if the DISP pixel values were known to already be log values required for subtraction. + + + + The result will be a signed value with a bit length one longer than the source frames. + When there is no difference between corresponding pixel values, the subtracted image pixel will have a value of 0. + If a pixel in the current frame has a greater value than in the mask frame, then the resulting frame shall have a positive value. If it has a lesser value, then the resulting frame shall have a negative value. +
+
+ VOI LUT + The VOI LUT operation applies only to grayscale values. + The value of interest (VOI) LUT transformation transforms the modality pixel values into pixel values that are meaningful for the user or the application. + + Photometric Interpretation (0028,0004) is ignored, since its effect is defined by the application of the grayscale transformations. + + The Softcopy VOI LUT Module in the Presentation State is analogous to the VOI LUT Module in an Image. + In the case of a linear transformation, the VOI LUT is described by the Window Center (0028,1050) and Window Width (0028,1051). In the case of a non-linear transformation, the VOI LUT is described by the VOI LUT Sequence. A VOI LUT Function (0028,1056) may be present to define a potentially non-linear interpretation (e.g., SIGMOID) of the values of Window Center (0028,1050) and Window Width (0028,1051). The rules for application of the VOI LUT are defined in . + The VOI LUT may have sections with negative slope. + + In the Basic Print Service Class a VOI LUT may not have negative slope. + + If a VOI LUT is part of both the Image and the Presentation State then the Presentation State VOI LUT shall be used instead of the Image VOI LUT. If a VOI LUT (that applies to the Image) is not present in the Presentation State, it shall be assumed to be an identity transformation. Any VOI LUT or equivalent values in the Image shall not be used. +
+
+ Presentation LUT + The Presentation LUT operation applies only to grayscale values. + The Presentation LUT transformation transforms the pixel values into P-Values, a device independent perceptually linear space as defined in . It may be an identity function if the output of the VOI LUT transformation is in P-Values. + + If the Presentation LUT and VOI LUT step are identity transformations, and the Mask Module is absent, then the output of the Modality LUT must be, by definition, P-Values. + + No output space other than P-Values is defined for the Grayscale Softcopy Presentation State Storage SOP Classes. + In the case of a linear transformation, the Presentation LUT is described by the Presentation LUT Shape (2050,0020). In the case of a non-linear transformation, the Presentation LUT is described by the Presentation LUT Sequence. The rules for application of the Presentation LUT are defined in . + + + + Since the grayscale transformation pipeline fully defines all transformations applied to the stored pixel values in the referenced image object, the value of Photometric Interpretation (0028,0004) in the referenced image object is ignored and overridden. This implies that either the creator of the presentation state chose a pipeline that reflects the Photometric Interpretation (0028,0004), or chose to ignore or override the Photometric Interpretation, and invert the image relative to what is specified by Photometric Interpretation. If the Modality LUT and VOI LUT do not have a negative slope, one can achieve the effect of inversion of the polarity of an image by choosing Presentation LUT Shape of IDENTITY or INVERSE that displays the minimum pixel value as white rather than black in the case of a Photometric Interpretation of MONOCHROME2, or black rather than white in the case of a Photometric Interpretation of + MONOCHROME1. If Presentation LUT Data is sent, then one can invert the value of the entries in the LUT table to achieve inversion of polarity. + + + The minimum P-Value (zero) always commands that the lowest intensity be displayed. + + + No separate Polarity transformation is defined. + + + + A Softcopy Presentation LUT Module is always present in a Presentation State. If a Presentation LUT is present in the Image then the Presentation State Presentation LUT shall be used instead of the Image Presentation LUT. +
+
+
+ Color Transformations +
+ Profile Connection Space Transformation + The Profile Connection Space Transformation operation applies only to color images, including true color (e.g., RGB) and pseudo-color (e.g., PALETTE COLOR) images, grayscale images for which a Palette Color LUT has been specified in the Presentation State, and the RGB output values of a blending operation. + The ICC Profile is an Input Profile. That is, it describes the color characteristics of a (possibly hypothetical) device that was used to generate the input color values. + The intent is that a rendering device will use this information to achieve color consistency. Typically this will be performed by calibration of the output device to create an ICC Display or Output Profile, the conversion of pixel values using the ICC Input Profile into Profile Connection Space, followed by conversion using the ICC Display or Output Profile into values suitable for rendering on the output device. However, the exact mechanisms used are beyond the scope of the standard to define. + + + + The means of achieving color consistency depends to a large extent on the nature of the material and the intent of the application. The process is more complicated than simply achieving colorimetric accuracy, which is trivial but does not produce satisfactory results. The transformations may take into account such matters as + + + physical factors such as the ambient light of the viewing environment (viewing flare) and the nature of different illuminants + + + psychovisual factors in the observer + + + the preferences of the observer + + + the consistency intent, whether it be to reproduce the colors perceived by an observer of + + + the original scene, + + + the media being reproduced, such as a print or transparency, as viewed under specified conditions. + + + + + + + Implementations of color management schemes are typically provided in operating systems, libraries and tool kits, and the exact details are usually beyond the control of the DICOM application developer. Accordingly, it is normally sufficient to define a source of pixel values, and a corresponding ICC Input Profile for the device that captured or generated them. + + + When a color image is rendered on grayscale display, the behavior is not defined. Since the L* value of a CIELab representation of the PCS is not dissimilar to the Barten model used in the GSDF, a reasonable approach would be to interpret it as a P-Value. + + + + An ICC Profile is always present in a Color, Pseudo-Color or Blended Presentation State. If an ICC Profile is present in the Image then the Presentation State ICC Profile shall be used instead of the Image ICC Profile. +
+
+ White Point (Informative) + D50 means black body radiation of an object at 5000 degrees K, and includes lots of red, which looks "natural". D65 is bluer, more like "cloudy days", but human eyes are more sensitive to blue. While monitors seem to be in the D50-D100 range, light boxes are about D110 (11000K). + The ICC PCS always uses a white point of D50. + In an ICC Input Profile, the chromaticAdaptationTag encodes a conversion of an XYZ color from the actual illumination source to the PCS illuminant (D50), and may be useful if the actual illumination source is not D50. The actual illumination source may also be defined in the mediaWhitePointTag. However, with a perceptual rendering intent, neither of these tags are required to be used by the color management system, nor do they have any specified rendering behavior (as opposed to their use with absolute and relative colorimetric rendering intents). + It is beyond the scope of DICOM to define a required or suggested white point for rendering, since an appropriate choice depends on a knowledge of the display device or media characteristics and the viewing environment. +
+
+
+ Common Spatial and Annotation Transformations + +
+ Common Spatial and Annotation Transformation Model + + + + + + +
+
+ The common spatial and annotation transformations apply to any device-independent values, whether they be grayscale P-Values or color PCS-Values, for any type of presentation state. + The values with which to render annotations are encoded as device-independent values, either as grayscale P-Values or as color PCS-Values. In the case of PCS-Values, CIELab values are encoded, and defined by reference to a D50 illuminant. + Grayscale presentation states may specify annotations in color for rendering on a color output device. + The mechanism for mapping grayscale P-Values and color PCS-values to the same display is implementation-dependent and not defined by the standard. +
+ Shutter + The Shutter transformation provides the ability to exclude the perimeter outside a region of an image. A gray level may be specified to replace the area under the shutter. + One form of this transformation uses the Display Shutter Module as used in the X-Ray Angiography Image Storage SOP Class, though it may be applied to any Image Storage SOP Instance, including single frame images. + Another form uses a bit-mapped overlay to indicate arbitrary areas of the image that should be excluded from display by replacement with a specified gray level, as described in the Bitmap Display Shutter Module. + + + + Since annotations follow the shutter operation in the pipeline, annotations in shuttered regions are not obscured and are visible. + + + Any shutter present in the referenced image object is ignored (i.e., not applied). + + + +
+
+ Pre-Spatial Transformation Annotation + The Pre-Spatial Transformation Annotation transformation includes the application of bit-mapped overlays as defined in the Overlay Plane Module, and free unformatted text or vector graphics as described in the Graphic Annotation Module that are defined in the image pixel space (as opposed to the displayed area space). +
+
+ Spatial Transformation + Some modalities may not deliver the image in the desired rotation and need to specify a rotation into the desired position for presentation. This transformation, specified in the Spatial Transformation Module, includes a rotation of 90, 180, 270 degrees clockwise followed by a horizontal flip (L <--> R). Rotation by an arbitrary angle is not supported. + In addition, selection of a region of the image pixel space to be displayed is specified in the Displayed Area Module. This may have the effect of magnifying (or minifying) that region depending on what physical size the display is instructed to render the selected region. If so, the method of interpolation (or sub-sampling) is implementation dependent. + + In particular the number of displayed pixels may be different from the number of image pixels as a result of: + + + minification (e.g., 1 display pixel for 4 image pixels), + + + magnification (4 display pixels for each image pixel), + + + interpolation (display pixels derived from values other than those in the image pixels), and + + + sub-sampling. + + + +
+
+ Post-Spatial Transformation Annotation + The Post-Spatial Transformation Annotation transformation includes the application of free unformatted text or vector graphics as described in the Graphic Annotation Module that are defined in the displayed area space (as opposed to the image pixel space). + This implies that the displayed area space is defined as being the image after all Spatial Transformations have been applied. + These annotations are rendered in the displayed space, though they may be anchored to points in either the displayed area or image pixel space. +
+
+
+ Blending Transformations + The grayscale to color blending transformation model applies only to a pair of grayscale values, one of which is first mapped to color and then superimposed upon the other. The resulting values are device independent color PCS-Values. This process is illustrated in . + For the purpose of this section, pixels are referred to as stored pixel values and transformations are defined as point operations on these values. However, it is likely that pixels from either or both the superimposed and underlying image sets will have been spatially resampled and hence interpolated or replicated. Such operations do not affect the conceptual pipeline. + +
+ Grayscale to Color Blending Transformation Model + + + + + + +
+
+
+ Underlying Image Pixels + The Modality LUT and VOI LUT transformations are applied to the stored pixel values of the underlying image. + The output range of the VOI LUT transformation depends either on the width of the linear window or the range of output values of the LUT defined by the LUT Descriptor. Conceptually, for the purpose of describing the succeeding blending operation, the smallest pixel value from the range is mapped to 0.0 and the largest pixel value is mapped to 1.0 and all intermediate values are linearly mapped to the [0.0..1.0] interval. +
+
+ Superimposed Image Pixels + The Modality LUT and VOI LUT transformations are applied to the stored pixel values of the superimposed image. + The full output range of the preceding VOI LUT transformation is implicitly scaled to the entire input range of the Palette Color LUT Transformation. + The output range of the RGB values in the Palette Color LUT Transformation depends on the range of output values of the LUT defined by the LUT Descriptors. Conceptually, for the purpose of describing the succeeding blending operation, a LUT entry of 0 is mapped to 0.0 and the largest LUT entry possible is mapped to 1.0 and all intermediate values are linearly mapped to the [0.0..1.0] interval. + + In practice, the Palette Color LUT output for the superimposed images is encoded in 8 or 16 bits and hence will have a range of 0 to 0xFF or 0xFFFF. + + The Palette Color LUT used is that encoded in the Blending Presentation State; any Palette Color LUTs or Supplemental Palette Color LUTs in the image instances are ignored. +
+
+ Blending Operation + The inputs to the blending operation are grayscale values from 0.0 to 1.0 from the underlying image (Yu) and RGB values from 0.0 to 1.0 from the superimposed image (RGBs), and an opacity value from 0.0 to 1.0 (A). + The output is a single image containing RGB values (RGBo) blended as: + Ro = Rs * A + Yu * (1-A) + Go = Gs * A + Yu * (1-A) + Bo = Bs * A + Yu * (1-A) +
+
+ Conversion to Profile Connection Space + The output of the blending operation is implicitly scaled to the gamut of the hypothetical device described by the ICC Input Profile, resulting in PCS-Values. +
+
+
+ Angiography Grayscale Transformations + The XA/XRF Grayscale Softcopy Presentation State Storage SOP Class supports a sequence of transformations that completely define the conversion of a stored image into a displayed image. + The sequence of transformations from stored pixel values into P-Values is explicitly defined in a conceptual model. The actual sequence implemented may differ but must result in the same appearance. describes this sequence of transformations. + +
+ XA/XRF Grayscale Image Transformation Model + + + + + + +
+
+
+ Mask + The Mask transformation consists of mask subtraction operations as specified by the Attributes of the XA/XRF Presentation State Mask Module and the Attribute Mask Visibility Percentage of the XA/XRF Presentation State Presentation Module. + The mask transformation may be applied in the case of multi-frame images for which other frames at a fixed frame position or time interval relative to the current frame may be subtracted from the current frame. Multiple mask frames may be averaged, and sub-pixel shifted before subtraction. Sub-pixel shift may be specified on a frame-by-frame base. Different pixel-shifts may be applied to more than one region of a contrast frame. + In the case of X-Ray images, the subtraction is specified to take place in a space logarithmic to X-Ray intensity. If the stored pixel values are not in a logarithmic space then a Pixel Intensity Relationship LUT shall be present in the XA/XRF Presentation Mask Module specifying a transformation into such a logarithmic space, otherwise it shall not be present. If a Modality LUT or Pixel Intensity Relationship LUT is present in the referenced image(s) it shall be ignored. The Pixel Intensity Relationship LUT can be specified on a frame-by frame base that can be different for mask and contrast frames. + + + + For images of the X-Ray Angiographic Image Storage SOP Class or X-Ray RF Image Storage SOP Class the XA/XRF Grayscale Softcopy Presentation State allows a Pixel Intensity Relationship LUT to be specified on a frame-by-frame base. This is an enhancement of the image Modality LUT that is only applicable for all frames of an image. + + + In the case of an XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the image is LOG, then even though a Modality LUT would be present in the image (to map pixel values back to linear X-Ray intensity), no Pixel Intensity Relationship LUT would be present in the presentation state for any frame since log values are required for subtraction. See . + In the case of Enhanced XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the frame is LOG, then even though a Pixel Intensity Relationship LUT would be present in the frame (to map pixel values back to linear X-Ray intensity, LUT Function (0028,9474) equals TO_LINEAR), no Pixel Intensity Relationship LUT would be present in the presentation state for that frame since log values are required for subtraction. See . + + + In the case of an XA or XRF image if the Pixel Intensity Relationship (0028,1040) in the image is LIN, then no Modality LUT would be present in the image, but a Pixel Intensity Relationship LUT would need to be present (to map pixel values to log values, LUT Function (0028,9474) equals TO_LOG) in the presentation state for all the frames since log values are required for subtraction. + In the case of an Enhanced XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the frame is LIN, then no Pixel Intensity Relationship LUT for the purpose to map pixel values back to linear X-Ray intensity (LUT Function (0028,9474) equals TO_LINEAR) would be present in the image, but a Pixel Intensity Relationship LUT would need to be present (to map pixel values to log values) in the presentation state for that frame since log values are required for subtraction. + + + In the case of an XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the image is DISP, then even though a Modality LUT may or may not be present in the image (to map pixel values back to linear to X-Ray intensity), a different Pixel Intensity Relationship LUT would be present in the presentation state if the creator of the presentation state could create a transformation from DISP pixel values to a logarithmic space for subtraction, or the Pixel Intensity Relationship LUT in the presentation state would be an identity transformation if the DISP pixel values were known to already be log values required for subtraction. + In the case of an Enhanced XA or XRF image, if the Pixel Intensity Relationship (0028,1040) in the image is OTHER, then even though a Pixel Intensity Relationship LUT may or may not be present for that frame (to map pixel values back to linear to X-Ray intensity), a different Pixel Intensity Relationship LUT would be present in the presentation state for that frame if the creator of the presentation state could create a transformation from OTHER pixel values to a logarithmic space for subtraction, or the Pixel Intensity Relationship LUT in the presentation state would be an identity transformation if the OTHER pixel values were known to already be log values required for subtraction. + + + Notes 2, 3 and 4 are summarized in + + + + + + + + + + + + + + + + + + + + + + + + + +
Summary of Providing a LUT Function for Subtraction
+ + Pixel Intensity Relationship (0028,1040) Attribute of the referenced SOP Instance + + + + The contents of Pixel Intensity Relationship LUT Sequence (0028,9422) in XA/XRF Presentation State Mask Module + +
+ LIN + + TO_LOG LUT provided +
+ LOG + + absent +
+ DISP or OTHER + + TO_LOG LUT provided, may be an identity +
+
+
+
+ Edge Enhancement + The Edge Enhancement transformation consists of filter operations to enhance the display of the pixel data as specified by the Attribute Display Filter Percentage of the XA/XRF Presentation State Presentation Module. +
+
+
+ Advanced Blending Transformations + The advanced blending transformation model applies to multiple color inputs and uses foreground blending or equal blending. + Several transformations in this IOD affect the input prior to its use in blending as depicted in . + Grayscale inputs that have no associated Color LUT information shall have the normal grayscale processing and then be converted to a full color image by setting R equals G equals B. + +
+ Color and Threshold Application + + + + + + +
+
+ Padding pixels in an input are given an opacity value zero and shall be set to 0 for Red, Green, and Blue. + The foreground method blends two inputs. The first input uses an opacity of Relative Opacity (0070,0403) and the second input uses an opacity of (1 - Relative Opacity (0070,0403) ). + If both the inputs are padding values then the result is padding value. + If one of the values is padding value then the result is the non-padding value. + If both pixels have values then result is Relative Opacity * first value + (1 - Relative Opacity) * second value. + +
+ Foreground Blending + + + + + + +
+
+ The Equal blending mode blends two or more inputs where for each pixel location the opacity is calculated as 1.0 divided by the number of non-padding pixels. The result pixel blends all non-padding pixels using the calculated opacity. + If an input pixel value is the padding-value then the Relative Opacity for that input pixel is zero. + If an input pixel value is not the padding value then the Relative Opacity for that pixel is 1 / (number of input pixels that are non-padding pixels). + The result value is the sum for all input pixels of the input pixel value * Relative Opacity. + If all the inputs pixels are padding values then the result is padding value. + +
+ Equal Blending + + + + + + +
+
+
+
+
+ Behavior of an SCP + In addition to the behavior for the Storage Service Class specified in Behavior of an SCP, the following additional requirements are specified for the Softcopy Presentation State Storage SOP Classes: + + + a display device acting as an SCP of these SOP Classes shall make all mandatory presentation Attributes available for application to the referenced images at the discretion of the display device user, for all Image Storage SOP Classes defined in the Conformance Statement for which the Softcopy Presentation State Storage SOP Class is supported. + + + a display device that is acting as an SCP of these SOP Classes and that supports compound graphics types shall display the graphics described in the Compound Graphic Sequence (0070,0209) and shall not display the Items in the Text Object Sequence (0070,0008) and Graphic Object Sequence (0070,0009) that have the same Compound Graphic Instance ID (0070,0226) value. + + + + Though it is not required, a display device acting as an SCP of the Blending Softcopy Presentation State Storage SOP Class may support the Spatial Registration Storage SOP Class in order to transform one Frame of Reference into another or to explicitly identify the relationship between members of two sets of images, and may be able to resample underlying and superimposed sets of images that differ from each other in orientation and in-plane and between-plane spatial resolution. + +
+
+ Conformance + In addition to the Conformance Statement requirements for the Storage Service Class specified in , the following additional requirements are specified for the Softcopy Presentation State Storage SOP Classes: +
+ Conformance Statement for an SCU + The following issues shall be documented in the Conformance Statement of any implementation claiming conformance to a Softcopy Presentation State Storage SOP Class as an SCU: + + + For an SCU of a Softcopy Presentation State Storage SOP Class that is creating a SOP Instance of the Class, the manner in which presentation related Attributes are derived from a displayed image, operator intervention or defaults, and how they are included in the IOD. + + + For an SCU of a Softcopy Presentation State Storage SOP Class, the Image Storage SOP Classes that are also supported by the SCU and may be referenced by instances of the Softcopy Presentation State Storage SOP Class. + + + For an SCU of a Softcopy Presentation State Storage SOP Class whether it supports the Compound Graphic Sequence (0070,0209) and specifies which compound graphic types can be generated, including additional private defined compound graphic types. + + +
+
+ Conformance Statement for an SCP + The following issues shall be documented in the Conformance Statement of any implementation claiming conformance to a Softcopy Presentation State Storage SOP Class as an SCP: + + + For an SCP of a Softcopy Presentation State Storage SOP Class that is displaying an image referred to by a SOP Instance of the Class, the manner in which presentation related Attributes are used to influence the display of an image. + + + For an SCP of a Softcopy Presentation State Storage SOP Class, the Image Storage SOP Classes that are also supported by the SCP and may be referenced by instances of the Softcopy Presentation State Storage SOP Class. + + + For an SCP of a Softcopy Presentation State Storage SOP Class whether it supports the Compound Graphic Sequence (0070,0209) and which compound graphic types can be rendered, including additional private defined compound graphic types. + + +
+
+
+ + Structured Reporting Storage SOP Classes (Normative) +
+ Overview + The Structured Reporting Storage SOP Classes extend the functionality of the Storage Service class (defined in ) to extend the SCP behavior and conformance requirements. +
+
+ Structured Reporting Storage SOP Class SCU and SCP Behavior +
+ Behavior of an SCU +
+ CAD SR SOP Classes + Rendering Intent concept modifiers in the Mammography CAD SR, Chest CAD SR and Colon CAD SR objects shall be consistent. Content items marked "For Presentation" shall not be subordinate to content items marked "Not for Presentation" or "Presentation Optional" in the content tree. Similarly, content items marked "Presentation Optional" shall not be subordinate to content items marked "Not for Presentation" in the content tree. + Content items referenced from another SR object instance, such as a prior Mammography CAD SR, Chest CAD SR or Colon CAD SR shall be inserted by-value in the new SR object instance, with appropriate original source observation context. It is necessary to update Rendering Intent, and referenced content item identifiers for by-reference relationships, within content items paraphrased from another source. +
+
+ Extensible SR SOP Class + The concept of extensibility implies that a recipient may encounter Content Items, Value Types and Relationship Types that are unanticipated and unsupported and hence potentially unrenderable. + An implementation shall identify in its Conformance Statement which Content Items, Value Types and Relationship Types it creates. +
+
+
+ Behavior of an SCP + An SCP intending to display or otherwise render a Structured Report shall convey its full meaning in an unambiguous manner, except as described in . + + "Full meaning" includes not just the Content Tree (i.e., the Items of the Content Sequence), but all Attributes of the Data Set that are necessary to properly interpret the Structured Report. This includes those Attributes that set the initial Observation Context for the Content Tree, i.e., the patient, procedure, and observer identifiers, and the Completion status and Verification status of the Structured Report. + + An Icon Image in an IMAGE reference has no meaning, and is not required to be rendered. + For a device, that is both an SCU and an SCP of these Storage SOP Classes, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for Structured Reporting Storage SOP Classes: + + + an SCP of this SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition associated with the SOP Class will be stored and may be accessed. + +
+ CAD SR SOP Classes + The Mammography CAD SR, Chest CAD SR and Colon CAD SR objects contain data not only for presentation to the clinician, but also data solely for use in subsequent mammography CAD analyses. + The SCU provides rendering guidelines via "Rendering Intent" concept modifiers associated with "Individual Impression/Recommendation", "Composite Feature" and "Single Image Finding" content items. The full meaning of the SR is provided if all content items marked "Presentation Required" are rendered down to the first instance of "Not for Presentation" or "Presentation Optional" for each branch of the tree. Use of the SCU's Conformance Statement is recommended if further enhancement of the meaning of the SR can be accomplished by rendering some or all of the data marked "Presentation Optional". Data marked "Not for Presentation" should not be rendered by the SCP; it is embedded in the SR content tree as input to subsequent CAD analysis work steps. + The SCP may further interpret whether or not to render a Single Image Finding that has Rendering Intent "Presentation Optional" by interpreting the value of the CAD Operating Point content item that is subordinate to the Rendering Intent, if present. If the CAD Operating Point content item is not present, then rendering of the Single Image Finding may be based on recommendations in the creator's DICOM Conformance Statement. For further information on the intended use of CAD Operating Point see . +
+
+ Extensible SR SOP Class + The concept of extensibility implies that a recipient may encounter Content Items, Value Types and Relationship Types that are unanticipated and unsupported and hence potentially unrenderable. + An implementation shall identify in its Conformance Statement which Content Items, Value Types and Relationship Types it supports. + Since it may not be possible to render the entire content in an unambiguous manner because of unrecognized content, an SCP intending to display or otherwise render an Extensible SR SOP Instance + + + shall convey a warning in the rendering to indicate that unsupported content is present and that this may affect the meaning of the rendering + + + shall identify in its Conformance Statement its behavior when encountering unsupported content + + +
+
+
+
+ Modification of SR Document Content + A device that is an SR Storage SOP Class SCU may modify information in a SOP Instance that it has previously sent or received. When this SOP Instance is modified and sent to an SCP, it shall be assigned a new SOP Instance UID if any of the following conditions are met: + + + addition, removal or update of any Attribute within the SR Document General Module or SR Document Content Module; + + + modification of the Series Instance UID (0020,000E); + + + modification of the Study Instance UID (0020,000D). + + +
+
+ Conformance + In addition to the Conformance Statement requirements for the Storage Service Class specified in , the following additional requirements are specified for Structured Reporting Storage SOP Classes: +
+ Conformance Statement for an SCU + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Structured Reporting Storage SOP Classes as an SCU: + + + The Image or other composite object Storage SOP Classes that are also supported by the SCU and may be referenced by instances of Structured Reporting Storage SOP Class. + + + The range of Value Types and Relationship Types that are supported by the SCU. + + + The conditions under which a new SOP Instance UID is generated for an existing SR Document. + + + If the implementation provides Query/Retrieve of Structured Reporting SOP Instances as an SCU, whether it supports the Optional Keys Concept Name Code Sequence or Content Template Sequence. + + + + The description of the Value Types and Relationship Types that are supported by the SCU is particularly important for the Extensible SR SOP Class. + +
+ CAD SR SOP Classes + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Mammography CAD SR SOP Class as an SCU: + + + Which types of detections and/or analyses the device is capable of performing: + + + From detections listed in Context Group 6014 Mammography Single Image Finding + + + From analyses listed in Context Group 6043 Types of Mammography CAD Analysis + + + + + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Chest CAD SR SOP Class as an SCU: + + + Which types of detections and/or analyses the device is capable of performing: + + + From detections listed in Context ID 6101 Chest Finding or Feature, or Context ID 6102 Chest Finding or Feature Modifier + + + From analyses listed in Context ID 6137 Types of CAD Analysis + + + + + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Colon CAD SR SOP Class as an SCU: + + + Which types of detections and/or analyses the device is capable of performing: + + + From detections listed in Context ID 6201 Colon Finding or Feature + + + From analyses listed in Context ID 6137 Types of CAD Analysis + + + + + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Mammography CAD SR, Chest CAD SR or Colon CAD SR SOP Classes as an SCU that creates instances: + + + Which optional content items are supported + + + Conditions under which content items are assigned Rendering Intent of "Presentation Optional", and whether a CAD Operating Point value will be included with each Single Image Finding that has Rendering Intent of "Presentation Optional" + + + Recommendations for the conditions under which content items with Rendering Intent of "Presentation Optional" should be rendered, based on CAD Operating Point or otherwise + + + Conditions under which content items are assigned Rendering Intent of "Not for Presentation" + + +
+
+ Ultrasound SR SOP Classes + The following shall be documented in the Conformance Statement of any SR creator implementation claiming conformance to the Simplified Adult Echo SR SOP Class as an SCU: + + + A list of all the measurement codes from supported by the device for use in . + + + A list of initial measurement codes supported by the device for use in Row 1 or 2 of . + + + Optionally, a table of the post-coordinated modifer values associated with each measurement code. + + + + + A list of any extension codes added to , , , , . + + +
+
+
+ Conformance Statement for an SCP + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Structured Reporting Storage SOP Class as an SCP: + + + For an SCP of a Structured Reporting Storage SOP Class that is displaying or otherwise rendering the structured report contained in a SOP Instance of the Class, the general form in which the structured report related Attributes are rendered. + + + For an SCP of a Structured Reporting Storage SOP Class, the Image or other composite object Storage SOP Classes that are also supported by the SCP and may be referenced by instances of the Structured Reporting Storage SOP Class, and whether or not they will be displayed or otherwise rendered. + + + For an SCP of a Structured Reporting Storage SOP Class that is displaying or otherwise rendering an image or other composite object referred to by a SOP Instance of the Class, the manner in which the structured report related Attributes (such as spatial coordinates and referenced presentation states) are used to influence the display of the image or object. + + + If the implementation supports Query/Retrieve of Structured Reporting SOP Instances as an SCP, whether it supports the Optional Keys Concept Name Code Sequence or Content Template Sequence. + + +
+ CAD SR SOP Classes + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Mammography CAD SR, Chest CAD SR or Colon CAD SR SOP Classes as an SCP: + + + Conditions under which the SCP will render content items with Rendering Intent concept modifier set to "Presentation Optional" + + +
+
+ Extensible SR SOP Class + The following shall be documented in the Conformance Statement of any implementation claiming conformance to the Extensible SR SOP Class as an SCP: + + + The behavior and warnings generated when encountering unsupported Content Items, Value Types and Relationship Types + + +
+
+
+
+ + Application Event Logging Service Class (Normative) +
+ Overview +
+ Scope + The Application Event Logging Service Class defines an application-level class-of-service that facilitates the network transfer of Event Log Records to be logged or recorded in a central location. + The Application Event Logging Service Class addresses the class of application specific logs (e.g., procedural event logs) that are managed by a medical application. The Application Event Logging Service Class does not specify the means of accessing the central logs. + + This Service Class does not address system security or audit logs that are managed by general system logging applications and may use non-DICOM protocols (e.g., SYSLOG). + +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Application Event Logging Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Application Event Logging Service Class are implemented using the DIMSE-N N-ACTION service as defined in . + The N-ACTION service conveys the following semantics: + + + The SCU notifies the SCP that an event has occurred that the SCP should record in a log. The Action Information of the N-ACTION-RQ contains the information about the event. + + + The SCP responds with a confirmation of the status of the recording action. + + + The association negotiation procedure is used to negotiate the supported SOP Classes. specifies the association procedure. The Application Event Logging Service Class does not support extended negotiation. + The release of an association shall not have any effect on the contents of the log managed by the SCP. +
+
+
+ Procedural Event Logging SOP Class Definition + The Procedural Event Logging SOP Class allows SCUs to report to an SCP the events that are to be recorded in a Procedure Log SOP Instance, as described in . This allows multiple devices participating in a Study to cooperatively construct a log of events that occur during that Study. + The multiple procedural events reported through this SOP Class are related by Patient ID, Study Instance UID, Study ID, and/or Performed Location. The mechanism by which multiple devices obtain these shared identifiers is not defined by this SOP Class. + + The Modality Worklist or UPS SOP Classes may be used for this purpose. For simple devices that cannot support worklist SOP classes, the SCP may be able to use Performed Location, or the SCU AE Title, to relate the use of the device to a particular procedure. + + The SCP may also provide for recording events for which the SCU does not provide identifiers for matching. The mechanism by which the SCP determines the association of such an unidentified event with the log for a specific procedure is not defined by this SOP Class. + + The network address and/or AE Title of the SCU may be used to identify the device as a participant in a particular procedure. + +
+ DIMSE Service Group + The DIMSE-N Services applicable to the Procedural Event Logging SOP Class are shown in . + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-ACTION + + M/M +
+ The DIMSE-N Services and Protocol are specified in . +
+
+ Operation + The DICOM AEs that claim conformance to this SOP Class as an SCU shall invoke the N-ACTION request. The DICOM AEs that claim conformance to this SOP Class as an SCP shall support the N-ACTION request. +
+ Action Information + The DICOM AEs that claim conformance to this SOP Class as an SCU and/or an SCP shall support the Action Type and Action Information in the N-ACTION-RQ as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Procedural Event Logging Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Record Procedural Event + + 1 + + Specific Character Set + + (0008,0005) + + 1C/1C + (Required if an extended or replacement character set is used) +
+ + + Patient ID + + (0010,0020) + + 2/2 +
+ + + Study Instance UID + + (0020,000D) + + 2/2 +
+ + + Study ID + + (0020,0010) + + 2/2 +
+ + + Synchronization Frame of Reference UID + + (0020,0200) + + 2/2 +
+ + + Performed Location + + (0040,0243) + + 2/2 +
+ + + + All other Attributes of the using + + + + + See + +
+
+ Study Matching Attributes + The SCU may provide Patient ID (0010,0020), Study Instance UID (0020,000D), Study ID (0020,0010), and/or Performed Location (0040,0243) Attributes to allow the SCP to match the N-ACTION with a Study for which a procedure log is being created. +
+
+ Synchronization Frame of Reference UID + The Synchronization Frame of Reference UID (0020,0200) Attribute identifies the temporal frame of reference for the Observation DateTime (0040,A032) Attributes in the Procedural Event record. If the Observation DateTime Attribute values are not synchronized in an identifiable Frame of Reference, the Attribute shall be zero length. +
+
+ Constraints on Attributes of the SR Document Content Module + The Procedural Event record shall be conveyed in a (top level) Content Item, and subsidiary Content Items, as specified by the SR Document Content Module definition in . + The top level and subsidiary Content Items shall be constructed in accordance with the Procedure Log IOD Content Constraints of . + + + + These constraints specify use of BTID 3001 Procedure Log defined in , and specific particular use of the Observation DateTime (0040,A032) Attributes. + + + TID 3001 requires the explicit identification of the Observer Context of the top level CONTAINER through TID 1002. + + + There may be multiple events (subsidiary Content Items) included in a single N-ACTION-RQ message. + + + +
+
+
+ Service Class User Behavior + The SCU shall request logging of events that occur during a Study, using the N-ACTION request primitive. + The SCU shall receive N-ACTION responses. The actions taken upon a response status of Failure, or upon non-response of the SCP, are implementation dependent. +
+
+ Service Class Provider Behavior + The SCP shall manage the creation of SOP Instances of the Procedure Log Storage Service. It shall receive, via the N-ACTION request primitive, requests for logging of events that occur during a Study. The SCP shall (consonant with application dependent constraints) incorporate those event records into a Procedure Log SOP Instance for the specified Study. + The SCP shall return, via the N-ACTION response primitive, the N-ACTION Response Status Code applicable to the associated action request. +
+
+ Status Codes + The Service Class specific status values defined for the N-ACTION Service are specified in . See for additional general response status codes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Response Status
+ + Service Status + + + + Response Status Code + + + + Further Meaning + +
+ Success + + 0000 + +
+ Warning + + B101 + + Specified Synchronization Frame of Reference UID does not match SCP Synchronization Frame of Reference +
+ Warning + + B102 + + Study Instance UID coercion; Event logged under a different Study Instance UID +
+ Warning + + B104 + + IDs inconsistent in matching a current study; Event logged +
+ Failure + + C101 + + Procedural Logging not available for specified Study Instance UID +
+ Failure + + C102 + + Event Information does not match Template +
+ Failure + + C103 + + Cannot match event to a current study +
+ Failure + + C104 + + IDs inconsistent in matching a current study; Event not logged +
+
+
+ Action Reply + With any response status indicating Success or Warning, the identifiers of the study into which the event has been logged shall be returned in the N-ACTION-RSP Action Reply as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + +
Procedural Event Logging Action Reply
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Record Procedural Event + + 1 + + Study Instance UID + + (0020,000D) + + 3/1 +
+ Patient ID + + (0010,0020) + + 3/1 +
+
+
+
+ Procedural Event Logging SOP Class UID + The Procedural Event Logging SOP Class shall be uniquely identified by the Procedural Event Logging SOP Class UID, which shall have the value "1.2.840.10008.1.40". +
+
+ Procedural Event Logging Instance Identification + The well-known UID of the Procedural Event Logging SOP Instance shall have the value "1.2.840.10008.1.40.1". +
+
+ Conformance Requirements + The DICOM AE's Conformance Statement shall be formatted as defined in . +
+ SCU Conformance + The SCU shall document in its Conformance Statement the behavior and actions that cause the SCU to generate an N-ACTION primitive (Procedural Event Notification). It shall specify the Template used for constructing the Event Information, and the Coding Schemes used for coded entries in the Event Information. + The SCU shall document the identifiers it sends for matching purposes, and how it obtains those Attributes (e.g., through a Modality Worklist query, manual entry, etc.). + The SCU shall document the behavior and actions performed when a success, warning, or failure status is received. + The SCU shall document the mechanisms used for establishing time synchronization and specifying the Synchronization Frame of Reference UID. +
+
+ SCP Conformance + The SCP shall document in its Conformance Statement how it uses the identifiers it receives for matching the N-ACTION (Procedural Event Notification) to a specific procedure. + The SCP shall document the behavior and actions that cause the SCP to generate a success, warning, or failure status for a received N-ACTION. + The SCP shall document the behavior and actions that cause the SCP to generate a Procedure Log SOP Instance including the received Event Information. + The SCP shall document how it assigns the value of the Observation Datetime (0040,A032) Attribute when the SCU-provided Synchronization Frame of Reference UID is absent, or differs from that of the SCP. +
+
+
+
+ Substance Administration Logging SOP Class Definition + The Substance Administration Logging SOP Class allows an SCU to report to an SCP the events that are to be recorded in a patient's Medication Administration Record (MAR) or similar log, whose definition is outside the scope of the Standard. This allows devices with DICOM protocol interfaces to report administration of diagnostic agents (including contrast) and therapeutic drugs, and implantation of devices. + The Substance Administration reported through this SOP Class is related to the MAR by Patient ID or Admission ID. The mechanism by which the SCU obtains this identifier is not defined by this SOP Class. + The log entry to the MAR is authorized by at least one of the Operators identified in the Operator Identification Sequence. The mechanism by which the SCU obtains these identifiers is not defined by this SOP Class. The SCP may refuse the log entry if none of the identified Operators is authorized to add entries to the MAR. The mechanism by which the SCP validates such authorization is not defined by this SOP Class. + + + + The SCP of this Service Class is not necessarily the Medication Administration Record system, but may be a gateway system between this DICOM Service and an HL7 or proprietary interface of a MAR system. Such implementation design is beyond the scope of the DICOM standard. + + + This SOP Class is not limited to only specifying medications, although the conventional name of the destination log is the Medication Administration Record. The SOP Class may also be used to record the implantation of therapeutic devices, including both drug-eluting and bare stents, prosthetic and cardiovascular devices, implantable infusion pumps, etc. + + + The application level authorization of Operators for the purpose of logging a MAR entry is distinct from any access control mechanism at the transport layer (see User Identity Association profiles in ). + + + +
+ DIMSE Service Group + The DIMSE-N Services applicable to the Substance Administration Logging SOP Class are shown in . + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-ACTION + + M/M +
+ The DIMSE-N Services and Protocol are specified in . +
+
+ Operation + The DICOM AEs that claim conformance to this SOP Class as an SCU shall invoke the N-ACTION request. The DICOM AEs that claim conformance to this SOP Class as an SCP shall support the N-ACTION request. +
+ Substance Administration Log Action Information + This operation allows an SCU to submit a Medication Administration Record log item or entry, providing information about a specific real-world act of Substance Administration that is the purview of the SCU. This operation shall be invoked through the DIMSE N-ACTION Service. + The Action Information Attributes are defined by the Substance Administration Log Module specified in . The DICOM AEs that claim conformance to this SOP Class as an SCU and/or an SCP shall support the Action Type and Action Information Attributes in the N-ACTION-RQ as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Substance Administration Logging N-ACTION Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Record Substance Administration Event + + 1 + + Specific Character Set + + (0008,0005) + + 1C/1C + (Required if an extended or replacement character set is used) +
+ Patient ID + + (0010,0020) + + 1C/1C + Either or both Patient ID and Admission ID shall be supplied by the SCU; the SCP shall support the Attribute if supplied +
+ Issuer of Patient ID + + (0010,0021) + + 3/2 +
+ Patient's Name + + (0010,0010) + + 2/2 +
+ Admission ID + + (0038,0010) + + 1C/1C + Either or both Patient ID and Admission ID shall be supplied by the SCU; the SCP shall support the Attribute if supplied +
+ Issuer of Admission ID + + (0038,0011) + + 3/2 +
+ Product Package Identifier + + (0044,0001) + + 1C/1C + Either or both Product Package Identifier and Product Name shall be supplied by the SCU; the SCP shall support the Attribute if supplied +
+ Product Name + + (0044,0008) + + 1C/1C + Either or both Product Package Identifier and Product Name shall be supplied by the SCU; the SCP shall support the Attribute if supplied +
+ Product Description + + (0044,0009) + + 3/3 +
+ Substance Administration DateTime + + (0044,0010) + + 1/1 +
+ Substance Administration Notes + + (0044,0011) + + 3/2 +
+ Substance Administration Device ID + + (0044,0012) + + 3/3 +
+ Administration Route Code Sequence + + (0054,0302) + + 2/2 +
+ >Code Value + + (0008,0100) + + 1/1 +
+ >Coding Scheme Designator + + (0008,0102) + + 1/1 +
+ >Code Meaning + + (0008,0104) + + 1/1 +
+ Substance Administration Parameter Sequence + + (0044,0019) + + 3/3 +
+ > All Attributes of the Substance Administration Parameter Sequence + + + + 3/3 +
+ Operator Identification Sequence + + (0008,1072) + + 1/1 +
+ >Person Identification Code Sequence + + (0040,1101) + + 1/1 +
+ >>Code Value + + (0008,0100) + + 1/1 +
+ >>Coding Scheme Designator + + (0008,0102) + + 1/1 +
+ >>Code Meaning + + (0008,0104) + + 1/1 +
+
+
+ Service Class User Behavior + The SCU shall request logging of substance administration events for a specified Patient using the N-ACTION request primitive. + The SCU shall receive N-ACTION responses. The actions taken upon a response status of Failure, or upon non-response of the SCP, are implementation dependent. +
+
+ Service Class Provider Behavior + The SCP shall receive, via the N-ACTION request primitive, requests for logging of substance administration events. The SCP shall incorporate those event records into a Medication Administration Record or similar log for the specified Patient. + + The patient's identify may be conveyed explicitly by Patient ID (0010,0020), or implicitly by Admission (i.e., Visit) ID (0038,0010). An institution may typically chose one or the other to use as the primary patient identifier at the point of care, e.g., printed on a bar coded wristband, the use of which may facilitate data entry for the log entry. However, in the "Model of the Real World for the Purpose of Modality-IS Interface" (see ), the Visit is subsidiary to the Patient; hence the Admission ID (0038,0010) may only be unique within the context of the patient, not within the context of the institution. The use of the Admission ID (0038,0010) Attribute to identify the Patient is only effective if the Admission ID (0038,0010) is unique within the context of the institution. + + The SCP shall support inclusion into the Medication Administration Record or similar log of values of all Type 1 and Type 2 Attributes for which the SCU has provided values. The SCP may convert these Attributes into a form appropriate for the destination log. + + The SCP may convert coded data to free text in the log, with loss of the specific code values, if the log does not support such coded data. + + The SCP shall return, via the N-ACTION response primitive, the N-ACTION Response Status Code applicable to the associated action request. +
+
+ Status Codes + The Service Class specific status values defined for the N-ACTION Service are specified in . See for additional general response status codes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Response Status
+ + Service Status + + + + Response Status Code + + + + Further Meaning + +
+ Success + + 0000 + +
+ Failure + + C10E + + Operator not authorized to add entry to Medication Administration Record +
+ C110 + + Patient cannot be identified from Patient ID (0010,0020) or Admission ID (0038,0010) +
+ C111 + + Update of Medication Administration Record failed +
+
+
+
+ Substance Administration Logging SOP Class UID + The Substance Administration Logging SOP Class shall be uniquely identified by the Substance Administration Logging SOP Class UID, which shall have the value "1.2.840.10008.1.42". +
+
+ Substance Administration Logging Instance UID + The well-known UID of the Substance Administration Logging SOP Instance shall have the value "1.2.840.10008.1.42.1". +
+
+ Conformance Requirements + The DICOM AE's Conformance Statement shall be formatted as defined in . +
+ SCU Conformance + The SCU shall document in its Conformance Statement the behavior and actions that cause the SCU to generate an N-ACTION-RQ primitive. + The SCU shall document how it obtains the Patient ID (0010,0020) or Admission ID (0038,0010) Attribute (e.g., through a Modality Worklist query, bar-code scan, manual entry, etc.). + The SCU shall document the behavior and actions performed when a success or failure status is received. +
+
+ SCP Conformance + The SCP shall document in its Conformance Statement how it uses the information it receives for adding data to a Medication Administration Record. + The SCP shall document the behavior and actions that cause the SCP to generate a success or failure status for a received N-ACTION-RQ. +
+
+
+
+ + Relevant Patient Information Query Service Class (Normative) +
+ Overview + The Relevant Patient Information Query Service Class defines an application-level class-of-service that facilitates the access to relevant patient information such as it is known at the time of query. + The query information model consists of two entities with a one-to-one relationship: the Patient and the Patient Information. + The Patient Information may be general, or specific to a particular imaging or procedure domain. A general SOP Class is defined along with some additional domain specific SOP Classes. +
+
+ DIMSE-C Service Group + One DIMSE-C Service is used in the construction of SOP Classes of the Relevant Patient Information Query Service Class. The following DIMSE-C operation is used. + + + C-FIND + + +
+ C-FIND Operation + SCPs of the Relevant Patient Information Query Service Class are capable of processing queries using the C-FIND operation as described in . The C-FIND operation is the mechanism by which queries are performed. The SCP shall provide Relevant Patient Information for at most one matching patient in the C-FIND response. +
+ C-FIND Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Relevant Patient Information Model and Template against which the C-FIND is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-FIND operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-FIND operation with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. +
+
+ Identifier + Both the C-FIND request and response contain an Identifier encoded as a Data Set (see ). +
+ Request Identifier Structure + An Identifier in a C-FIND request shall contain: + + + Key Attributes with values to be matched against the values of Attributes specified in the SOP Class. + + + Content Template Sequence (0040,A504), which shall include a single sequence item containing the Template Identifier (0040,DB00) and Mapping Resource (0008,0105) Attributes, to identify the template structure to use in the matching C-FIND responses. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Request Identifier. It shall not be included otherwise. + + + The Key Attributes and values allowable for the query are defined in the SOP Class definition for the Relevant Patient Information Model. +
+
+ Response Identifier Structure + The C-FIND response shall not contain Attributes that were not in the request or specified in this section. + An Identifier in a C-FIND response shall contain: + + + Key Attributes with values corresponding to Key Attributes contained in the Identifier of the request. + + + Content Template Sequence (0040,A504), which shall include a single sequence item containing the Template Identifier (0040,DB00) and Mapping Resource (0008,0105) Attributes, to identify the template structure used in the C-FIND response. The values shall be the same as specified in the Request Identifier. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Response Identifier. It shall not be included otherwise. The C-FIND SCP is not required to return responses in the Specific Character Set requested by the SCU if that character set is not supported by the SCP. The SCP may return responses with a different Specific Character Set. + + +
+
+ Relevant Patient Information Templates + Templates used in the Relevant Patient Information query are defined in . + The template specified in the Request Identifier shall not use by-reference relationships. +
+
+
+ Status + + defines the status code values that might be returned in a C-FIND response. Fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-FIND Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Out of Resources + + A700 + + (0000,0902) +
+ Identifier Does Not Match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ Unable to process + + C000 + + (0000,0901) + (0000,0902) +
+ More than one match found + + C100 + + (0000,0901) + (0000,0902) +
+ Unable to support requested template + + C200 + + (0000,0901) + (0000,0902) +
+ Cancel + + Matching terminated due to Cancel request + + FE00 + + None +
+ Success + + Success. Matching is complete - No final Identifier is supplied. + + 0000 + + None +
+ Pending + + Current Match is supplied. + + FF00 + + Identifier +
+ + Status Codes are returned in DIMSE response messages (see ). The code values stated in column "Status Codes" are returned in Status Command Element (0000,0900). + +
+
+
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation procedure specified in shall be used to negotiate the supported SOP Class. + SOP Class Extended Negotiation is not defined for this Service Class. +
+
+ DIMSE-C C-FIND Service + The DIMSE-C C-FIND service is the operation by which relevant patient information is queried and provided. +
+ Conventions + Key Attributes in the Request Identifier serve two purposes. They may be used as Matching Key Attributes and Return Key Attributes. Matching Key Attributes may be used for matching (criteria to be used in the C-FIND request to determine whether an entity matches the query). Return Key Attributes may be used to specify desired return Attributes (what elements in addition to the Matching Key Attributes have to be returned in the C-FIND response). + Matching Key Attributes may be of Type "required" (R) or "optional" (O). Return Key Attributes may be of Type 1, 1C, 2, 2C, 3 as defined in . +
+
+ Service Definition + Two peer DICOM AEs implement this Relevant Patient Information Query Service Class with one serving in the SCU role and one serving in the SCP role. The SOP Class is implemented using the DIMSE-C C-FIND service as defined in . + Only a baseline behavior of the DIMSE-C C-FIND is used in this Service Class. + A C-FIND service conveys the following semantics: + + + The SCU requests that the SCP perform a match for the Matching Keys and return values for the Return Keys that have been specified in the Identifier of the request, against the Relevant Patient Information that the SCP possesses. + + In this Annex, the term "Identifier" refers to the Identifier service parameter of the C-FIND service as defined in . + + + + The SCP generates a C-FIND response for at most one match with an Identifier containing the values of all Matching Key Attributes and all known Return Key Attributes requested. The response contains one relevant patient information instance in the form that matches the Template that was requested. This response shall contain a status of Pending. + + + When the process of matching is complete, with zero or one match, a C-FIND response is sent with a status of Success and no Identifier. + + + A Failed response to a C-FIND request indicates that the SCP is unable to process the request. This shall be used to indicate that the requested template is not supported by the SCP, or that more than one match was found by the SCP. + + + The SCU may cancel the C-FIND service by issuing a C-FIND-CANCEL request at any time during the processing of the C-FIND service. The SCP will interrupt all matching and return a status of Canceled. + + The SCU needs to be prepared to receive C-FIND responses sent by the SCP until the SCP finally processes the C-FIND-CANCEL request. + + + +
+
+ Relevant Patient Information Model SOP Classes +
+ Relevant Patient Information Model + In order to serve as a Service Class Provider (SCP) of one or more Relevant Patient Information Model SOP Classes, a DICOM Application Entity (AE) possesses relevant information about patients. This information is organized into a Relevant Patient Information Model. + The SOP Classes are composed of both the Information Model and a DIMSE-C Service Group. +
+ E/R Model + The E/R Model consists of Patient and Structured Information, with no relationship to other Information Entities in the DICOM Information model. + +
+ Relevant Patient Information E/R Model + + + + + + +
+
+ The Patient IE includes the Attributes of the Patient Identification and Patient Demographics Modules. + The Structured Information IE includes Attributes that are not inherently related to a real-world entity, but are interpreted through their coded content. This includes the Attributes of the Structured Document Content Module, which in the case of the Relevant Patient Information Query Service has its content constrained by specified templates to convey patient related information. Also included in the Structured Information IE are Attributes of the SOP Common and Common Instance Reference Modules that support the interpretation of coded data, or support access to referenced information objects identified in the coded data. +
+
+ Relevant Patient Information Attributes + + defines the Attributes of the Relevant Patient Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Relevant Patient Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + Patient + +
+ Patient's Name + + (0010,0010) + + - + + 1 + +
+ Patient ID + + (0010,0020) + + R + + 1 + + Shall be present in the Request Identifier. + Shall be retrieved with Single Value Matching. + + Since only one response is expected, this is a unique key. + +
+ Issuer of Patient ID + + (0010,0021) + + R + + 2 + + Shall be retrieved with Single Value Matching. + In situations where there are multiple issuers, this key constrains matching of Patient ID (0010,0020) to a domain in which the Patient ID (0010,0020) is unique. +
+ Patient's Birth Date + + (0010,0030) + + - + + 2 + +
+ Patient's Sex + + (0010,0040) + + - + + 2 + +
+ + All other Attributes of the + + + + + - + + 3 + +
+ + All other Attributes of the + + + + + - + + 3 + +
+ + Structured Information (SR Document Content Module) + +
+ Observation DateTime + + (0040,A032) + + - + + 1 + +
+ Value Type + + (0040,A040) + + - + + 1 + + See . +
+ Concept Name Code Sequence + + (0040,A043) + + - + + 1 + + See . +
+ >Code Value + + (0008,0100) + + - + + 1 + +
+ >Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >Coding Scheme Version + + (0008,0103) + + - + + 1C + + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ + >All other Attributes of the Concept Name Code Sequence + + + + + + + + + +
+ Content Sequence + + (0040,A730) + + - + + 2 + + See . +
+ >All Attributes of the Content Sequence + + + - + + - + + Content Items as provided by the SCP. Requirements on Content Item Attribute Types shall be in accordance with the definitions in the SR Document Content Module. +
+ HL7 Structured Document Reference Sequence + + (0040,A390) + + - + + 1C + +
+ >Referenced SOP Class UID + + (0008,1150) + + - + + 1 + +
+ >Referenced SOP Instance UID + + (0008,1155) + + - + + 1 + +
+ >HL7 Instance Identifier + + (0040,E001) + + - + + 1 + +
+ >Retrieve URI + + (0040,E010) + + - + + 3 + +
+ + Structured Information (Common Instance Reference Module) + +
+ Studies Containing Other Referenced Instances Sequence + + (0008,1200) + + - + + 1C + + Required if Content Sequence (0040,A390) includes Content Items that reference SOP Instances that use the Patient/Study/Series/Instance information model. +
+ >Referenced Series Sequence + + (0008,1115) + + - + + 1 + +
+ >>Series Instance UID + + (0020,000E) + + - + + 1 + +
+ >>Referenced Instance Sequence + + (0008,114A) + + - + + 1 + +
+ >>>Referenced SOP Class UID + + (0008,1150) + + - + + 1 + +
+ >>>Referenced SOP Instance UID + + (0008,1155) + + - + + 1 + +
+ The Attributes in are not part of the Information Model; their inclusion in the C-FIND request and response identifier are governed by rules in sections and , respectively. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Additional C-FIND Identifier Attributes
+ + Attribute Name + + + + Tag + + + Type in Request Identifier + + Type in Response Identifier + + + Remark + +
+ Content Template Sequence + + (0040,A504) + + 1 + + 1 + +
+ >Mapping Resource + + (0008,0105) + + 1 + + 1 + +
+ >Template Identifier + + (0040,DB00) + + 1 + + 1 + +
+ Specific Character Set + + (0008,0005) + + 1C + + 1C + + Required if expanded or replacement character sets are used. See , +
+
+
+ Relevant Patient Information Attribute Descriptions + Concept Name Code Sequence (0040,A043) in a C-FIND Response shall have one sequence item that identifies the Root node concept of the returned structure. This shall be the same as the Concept Name of the first row of the template identified in the Content Template Sequence (0040,A504) in the Identifier. The Concept Name Code Sequence (0040,A043) shall always be sent zero length in the Request Identifier. + The Value Type (0040,A040) applies to the Concept Name Code Sequence (0040,A043), and shall be the same as the Value Type (0040,A040) of the first row of the template identified in the Content Template Sequence (0040,A504) in the Identifier. + The Content Sequence (0040,A730) is a potentially recursively nested Sequence of Items, as described in , SR Document Content Module. The Content Sequence shall always be sent zero length in the Request Identifier. The Content Sequence in the data set of the Response shall contain the content items of the requested template. +
+
+
+ Conformance Requirements + An implementation may conform to the Relevant Patient Information Model SOP Classes as an SCU and/or as an SCP. + The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that conforms to one or more of the Relevant Patient Information Model SOP Classes shall support queries against the Relevant Patient Information Model described in using the baseline C-FIND SCU Behavior described in . + An implementation that conforms to one or more of the Relevant Patient Information Model SOP Classes as an SCU shall state in its Conformance Statement which SOP Class(es) it supports, and which Root template(s) it may request in a query if not specified by the SOP Class. The Conformance Statement shall also state the definition of any supported template extensions. +
+
+ SCP Conformance + An implementation that conforms to one or more of the Relevant Patient Information Model SOP Classes shall support queries against the Relevant Patient Information Model described in using the baseline C-FIND SCP Behavior described in . + An implementation that conforms to one or more of the Relevant Patient Information Model SOP Classes as an SCP shall state in its Conformance Statement which SOP Class(es) it supports, and which Root template(s) it will support in a query response if not specified by the SOP Class. The Conformance Statement shall also state the definition of any supported template extensions. + An implementation that conforms to one or more of the Relevant Patient Information Model SOP Classes as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching, and encoding responses. +
+
+
+ SOP Classes + The Relevant Patient Information Model SOP Classes in the Relevant Patient Information Query Service Class identify the Relevant Patient Information Model, and the DIMSE-C operation supported. In some instances a Root template is specified. The Standard SOP Classes are defined in : + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Classes for the Relevant Patient Information Model
+ + SOP Class Name + + + + SOP Class UID + + + + Root Template + +
+ General Relevant Patient Information Query + + 1.2.840.10008.5.1.4.37.1 + + TID 9007 General Relevant Patient Information, or from the list in + +
+ Breast Imaging Relevant Patient Information Query + + 1.2.840.10008.5.1.4.37.2 + + TID 9000 Relevant Patient Information for Breast Imaging +
+ Cardiac Relevant Patient Information Query + + 1.2.840.10008.5.1.4.37.3 + + TID 3802 Cardiovascular Patient History +
+ + The list of Root templates for the General Relevant Patient Information Query is extensible. + +
+
+
+
+ Relevant Patient Information Query Example (Informative) + Moved to . +
+
+ + Instance Availability Notification Service Class (Normative) +
+ Overview +
+ Scope + The Instance Availability Notification Service Class defines an application-level class-of-service that allows one DICOM AE to notify another DICOM AE of the presence and availability of SOP instances that may be retrieved. The AE from which such SOP Instances can later be retrieved may or may not be the SCU performing the notification. + + An example of usage of this Service Class is for the receiver of the instances to provide notification of their arrival and availability for subsequent workflow steps to a different entity, such as a separate workflow manager. + + The SCU implementation defines the conditions under which it provides the notification. Certain SCUs may provide notification for arbitrary sets of SOP Instances, while other SCUs may provide notification when they determine that the instances associated with a Procedure Step or a Requested Procedure are available. The SCU is required to document in its Conformance Statement the nature of its notification decisions (e.g., frequency of notifications, retrieve capabilities and latency, etc.). + Once the SCU has provided notification about availability of the SOP Instances, the SCP may use that information in directing further workflow, such as in populating the Input Information Sequence when forming a Unified Procedure Step. These types of policies are outside the scope of this Standard, however, the SCP is required to document these policies in its Conformance Statement. + The SCU of this Service Class is not required to assure that the study, procedure step or any workflow-related entity is "complete"; indeed no semantics other than the concept of "availability" is expressed or implied by the use of this service. + + + + The Performed Workitem Code Sequence (0040,4019) Attribute of a referenced GP-PPS instance may provide the specific description of the work item that triggered the Instance Availability Notification. + + + The Instance Availability Notification is typically a service of the composite instance Storage SCP, since that application is responsible for making the instances available. The Instance Availability Notification allows that application to report the specific Retrieve AE Title, which may differ from the Storage Service AE Title, and may vary with different instance SOP Classes, or may vary over time. + + + +
+
+
+ Conformance Overview + The Instance Availability Notification Service Class consists of a single SOP Class: the Instance Availability Notification SOP Class. + The SOP Class specifies Attributes, operations, and behavior applicable to the SOP Class. The conformance requirements shall be specified in terms of the Service Class Provider (SCP) and the Service Class User (SCU). + The Instance Availability Notification Service Class uses the Instance Availability Notification IOD as defined in and the N-CREATE DIMSE Service specified in . +
+
+ Instance Availability Notification SOP Class +
+ DIMSE Service Group + The DIMSE Services shown in are applicable to the Instance Availability Notification IOD under the Instance Availability Notification SOP Class. + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ The DIMSE Services and Protocols are specified in . + + Though the terminology "notification" is used for this Service Class, the notification is in fact performed through Operations rather than Notifications. + +
+
+ Operations + The Application Entity that claims conformance to this SOP Class as an SCU shall be permitted to invoke the following operations and the Application Entity that claims conformance as an SCP shall be capable of providing the following operations. +
+ N-CREATE Instance Availability Notification SOP Instance + This operation allows an SCU to create an instance of the Instance Availability Notification SOP Class and to provide availability information about Instances that are under the control of the SCU. This operation shall be invoked through the DIMSE N-CREATE Service. +
+ Attributes + The Attribute list of the N-CREATE is defined as shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Instance Availability Notification SOP Class N-CREATE Attributes
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + +
+ Specific Character Set + + (0008,0005) + + 1C/1C + (Required if an extended or replacement character set is used) +
+ + All other Attributes of the + + + + 3/3 +
+ Referenced Performed Procedure Step Sequence + + (0008,1111) + + 2/2 +
+ >Referenced SOP Class UID + + (0008,1150) + + 1/1 +
+ >Referenced SOP Instance UID + + (0008,1155) + + 1/1 +
+ >Performed Workitem Code Sequence + + (0040,4019) + + 2/2 +
+ >>Code Value + + (0008,0100) + + 1/1 +
+ >>Coding Scheme Designator + + (0008,0102) + + 1/1 +
+ >>Code Meaning + + (0008,0104) + + 1/1 +
+ + >>All other Attributes of the Performed Workitem Code Sequence + + + 3/3 +
+ Study Instance UID + + (0020,000D) + + 1/1 +
+ Referenced Series Sequence + + (0008,1115) + + 1/1 +
+ >Series Instance UID + + (0020,000E) + + 1/1 +
+ >Referenced SOP Sequence + + (0008,1199) + + 1/1 +
+ >>Referenced SOP Class UID + + (0008,1150) + + 1/1 +
+ >>Reference SOP Instance UID + + (0008,1155) + + 1/1 +
+ >>Instance Availability + + (0008,0056) + + 1/1 +
+ >>Retrieve AE Title + + (0008,0054) + + 1/1 +
+ >>Retrieve Location UID + + (0040,E011) + + 3/3 +
+ >>Retrieve URI + + (0040,E010) + + 3/3 +
+ >>Retrieve URL + + (0008,1190) + + 3/3 +
+ >>Storage Media File-Set ID + + (0088,0130) + + 3/3 +
+ >>Storage Media File-Set UID + + (0088,0140) + + 3/3 +
+
+
+ Service Class User + The SCU shall specify in the N-CREATE request primitive the SOP Class and SOP Instance UIDs of the Instance Availability Notification SOP Instance that is created and for which Attribute Values are to be provided. + The SCU shall provide Attribute values for the Instance Availability Notification SOP Class Attributes as specified in . + The use of additional optional Attributes by the SCU is forbidden. + + The reason for forbidding optional Attributes is to prevent the use of Standard Extended SOP Classes that might add contextual information such as patient and procedure identifiers. + + The encoding rules for Instance Availability Notification Attributes are specified in the N-CREATE request primitive specification in . + There are no requirements on when N-CREATE requests are required to be performed. + In particular, there are no requirements that notification about the availability of the first instance of a Performed Procedure Step or Study be provided upon its reception, nor that availability notification be provided when an entire set of instances comprising a completed Performed Procedure Step or Study are available, though these are typical and common scenarios. +
+
+ Service Class Provider + The SCP shall return, via the N-CREATE response primitive, the N-CREATE Response Status Code applicable to the associated request. +
+
+ Status Codes + There are no specific status codes. See for response status codes. +
+
+
+
+ Instance Availability Notification SOP Class UID + The Instance Availability Notification SOP Class shall be uniquely identified by the Instance Availability Notification SOP Class UID, which shall have the value "1.2.840.10008.5.1.4.33". +
+
+ Conformance Requirements + Implementations shall include within their Conformance Statement information as described below. + An implementation may conform to this SOP Class as an SCU or as an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that is conformant to this SOP Class as an SCU shall meet conformance requirements for the operations that it invokes. +
+ Operations + Any Attributes for which Attribute Values may be provided (using the N-CREATE) by the SCU shall be enumerated in the SCU Conformance Statement. The SCU Conformance Statement shall be formatted as defined in . + An implementation that conforms to this SOP Class as an SCU shall specify under which conditions during the performance of real-world activities it will create the SOP Class Instance. + The SCU Conformance Statement shall specify what is meant by each reported value of Instance Availability (0008,0056). + The SCU Conformance Statement shall describe the relationship between the Instance Availability Notification and the Performed Procedure Step SOP Classes, if the latter are supported. +
+
+
+ SCP Conformance + An implementation that is conformant to this SOP Class as an SCP shall meet conformance requirements for the operations that it performs. +
+ Operations + The SCP Conformance Statement shall be formatted as defined in . + The SCP Conformance Statement shall provide information on the behavior of the SCP (in terms of real world activities) for each reported value of Instance Availability (0008,0056). + The SCP Conformance Statement shall describe the behavioral relationship between the Instance Availability Notification and the Performed Procedure Step SOP Classes, if the latter are supported. +
+
+
+
+
+ + Media Creation Management Service Class (Normative) +
+ Overview +
+ Scope + The Media Creation Management Service Class defines a mechanism by which an SCU can instruct a device to create Interchange Media containing a set of Composite SOP Instances that have already been transferred to the media creation device using the Storage Service Class. + This Service Class does not address archival storage requirements. It is intended only for the management of media creation devices. There is no requirement by the Standard that an SCP of this Service Class will commit to taking responsibility for archival of Composite Instances, such that an SCU may then discard them. Such behavior is entirely outside the scope of the Standard. In other words, Media Creation does not imply Storage Commitment. + The application profile(s) for the set of instances, which implies the form of the media created (i.e., CD, DVD or MOD), can either be left to the discretion of the SCP, or explicitly specified in the media creation request. In the latter case, if the device is unable to create the requested profiles, an error shall be returned. + + + + More than one profile may be requested or used by default, since the requested set of instances may not be compatible with a single profile. DICOM media may always contain instances written by more than one profile. See . + + + It is the responsibility of the SCU to negotiate and store instances with an appropriate Transfer Syntax should a specific Transfer Syntax be required by a requested profile. The SCP is not required to support compression or decompression of stored instances in order to convert stored instances into a form suitable for a requested profile. It may do so, if so requested, but the level of lossy compression would be at the discretion of the SCP. If the degree of compression is important to the application, then the SCU may compress the images before sending them to the SCP. + + + + The request controls whether or not a label is to be generated on the media, be it from information contained in the instances (such as patient demographics) or from text explicitly specified in the request. + + + + An SCP may or may not be physically capable of labeling the media. This capability is outside the scope of conformance to the Standard. Inability to create a label is not an error. + + + De-identification of instances (and labels), such as for teaching file media or clinical trial media is the responsibility of the SCU and is outside the scope of this service. That is, the SCU must de-identify the composite instances before sending them, prior to the media creation request. + + + + The Service Class contains a limited capability to return status information. A media creation request may initially either fail or be accepted. Subsequently, the SCP may be polled as to the status of the request (idle, pending/creating, successful or failed) by the SCU on the same or on a separate Association. There is no asynchronous notification. There is no dependence on the duration or persistence of an Association. + + There is no requirement to manage the handling of transient failures (such as an empty supply of blank media or labels or ink). Whether or not the SCP queues stored instances and requests in such cases, or fails to accept the request, is outside the scope of the Standard. + +
+
+
+ Conformance Overview + The application-level services addressed by this Service Class are specified via the Media Creation Management SOP Class. + The Media Creation Management SOP Class specifies Attributes, operations and behavior applicable to the SOP Class. The conformance requirements shall be specified in terms of the Service Class Provider (SCP) and the Service Class User (SCU). + The Media Creation Management Service Class uses the Media Creation Management IOD as defined in and the N-CREATE, N-ACTION and N-GET Services specified in . +
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation rules as specified in shall be used to negotiate the supported SOP Classes. + Support for the SCP/SCU role selection negotiation is not applicable. The SOP Class Extended Negotiation is not defined for this Service Class. +
+
+
+ Media Creation Management SOP Class + The SCU transmits the SOP Instances to the SCP using the Storage Service Class. The request for media creation is transmitted to the SCP and contains a list of references to one or more SOP Instances. Success or failure of media creation is subsequently indicated by the SCU requesting the status from the SCP on the same or a separate association. +
+ DIMSE Service Group + The following DIMSE-N Services are applicable to the Media Creation Management SOP Class. + + + + + + + + + + + + + + + + + + + + + + +
DIMSE-N Services Applicable to Media Creation Management
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-ACTION + + M/M +
+ N-GET + + U/M +
+ The DIMSE-N Services and Protocol are specified in . +
+
+ Operations + The DICOM AEs that claim conformance to this SOP Class as an SCU shall invoke the N-CREATE and the N-ACTION operations. The DICOM AEs that claim conformance to this SOP Class as an SCP shall support the N-CREATE, the N-ACTION and the N-GET operations. +
+ Create a Media Creation Request + The Create a Media Creation Request operation allows an SCU to create an instance of the Media Creation Management SOP Class and initialize Attributes of the SOP Class. The SCP uses this operation to create a new media creation request containing the set of SOP Instances that shall be included in the Interchange Media. This operation shall be invoked through the N-CREATE primitive +
+ Attributes + The DICOM AEs that claim conformance to this SOP Class as an SCU may choose to provide a subset of the Attributes maintained by the SCP. The DICOM AEs that claim conformance to this SOP Class as an SCP shall support a subset of the Media Creation Management specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Media Creation Management - N-CREATE Attributes
+ + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Specific Character Set + + (0008,0005) + + 1C/1C (Required if expanded or replacement character set is used) +
+ Storage Media File-Set ID + + (0088,0130) + + 3/3 + See . +
+ Storage Media File-Set UID + + (0088,0140) + + 3/3 + See . +
+ Label Using Information Extracted From Instances + + (2200,0001) + + 3/1C + See . +
+ Label Text + + (2200,0002) + + 3/1C + See . +
+ Label Style Selection + + (2200,0003) + + 3/1C + See . +
+ Barcode Value + + (2200,0005) + + 3/3 + See + +
+ Barcode Symbology + + (2200,0006) + + 3/3 + See + +
+ Media Disposition + + (2200,0004) + + 3/3 + See . +
+ Allow Media Splitting + + (2200,0007) + + 3/1C + See + +
+ Allow Lossy Compression + + (2200,000F) + + 3/1C + See + +
+ Include Non-DICOM Objects + + (2200,0008) + + 3/1C + See + +
+ Include Display Application + + (2200,0009) + + 3/1C + See + +
+ Preserve Composite Instances After Media Creation + + (2200,000A) + + 3/3 +
+ Referenced SOP Sequence + + (0008,1199) + + 1/1 +
+ >Referenced SOP Class UID + + (0008,1150) + + 1/1 +
+ >Referenced SOP Instance UID + + (0008,1155) + + 1/1 +
+ >Requested Media Application Profile + + (2200,000C) + + 3/1 + See . +
+ >Icon Image Sequence + + (0088,0200) + + 3/1C + See . +
+
+ Storage Media File-Set Attributes + If present, the Storage Media File-Set ID (0088,0130) and Storage Media File-Set UID (0088,0140) shall be used on the media created. If absent, the media shall contain values generated by the SCP. + If the media request will not fit on a single volume (single piece or side of media), then whether or not the SCP ignores Storage Media File-Set ID (0088,0130), or uses it as a prefix and appends information to distinguish volumes, is implementation dependent. Different values of Storage Media File-Set UID (0088,0140) shall be used for different volumes. + If multiple copies are requested, the same Storage Media File-Set ID (0088,0130) and Storage Media File-Set UID (0088,0140) shall be used on all copies. + + Care should be taken with multiple copies written to rewritable media that their contents do not diverge even though their identifiers are identical. + +
+
+ Requested Media Application Profile + The Requested Media Application Profile (2200,000C), if present, shall be used by the SCP for the specified SOP Instance. If absent for a particular instance, the choice of Media Application Profile for that instance shall be at the discretion of the SCP. + + + + Different Media Application Profiles may be used for different instances on the same piece of media. + + + The form of the DICOMDIR directory records that the SCP must create may be significantly influenced by the media application profiles used. + + + +
+
+ Icon Image Sequence + The Icon Image Sequence (0088,0200), if present: + + + shall be used by the SCP for inclusion in the instance-level DICOM Directory Record for the specified SOP Instance, if the Media Application Profile requires its inclusion, and the icon supplied by the SCU meets the requirements of the profile + + + may be used by the SCP for inclusion in the instance-level DICOM Directory Record for the specified SOP Instance, if the Media Application Profile does not require its inclusion + + + If absent for a particular instance, the choice of Media Application Profile for that instance dictates whether or not the SCP is required to create its own Icon Image Sequence (0088,0200) from the contents of the SOP Instance. + + + + Some Media Application Profiles require the inclusion of an Icon Image Sequence (0088,0200) in the directory records. + + + Some Media Application Profiles specify constraints on the form of the Icon Image Sequence (0088,0200). + + + The SCP may choose to extend the Media Application Profile by generating and including icons anyway. + + + +
+
+ Labeling + The SCP may or may not have the capability to print a label on (or for) the media. If it does, then the following SCP behavior shall apply and the specified Attributes are required to be supported by the SCP. + The Label Using Information Extracted From Instances (2200,0001) Attribute is a flag that instructs the SCP whether or not to create any label using the Patient and Study information contained within the instances themselves. + + The SCP may implement whatever it considers to be an appropriate subset of any Attributes of any Modules at the Patient, Specimen and Study entities in the DICOM Information Model specified in . Typically included are such Attributes as Patient Name (0010,0010), Patient ID (0010,0020), Study ID (0020,0010), and Study Date (0008,0020). + + The Label Text (2200,0002) Attribute is additional text that the SCP shall include on any label, either in addition to or instead of any extracted demographics, depending on the value of Label Using Information Extracted From Instances (2200,0001). + The Label Style Selection (2200,0003) Attribute is a code string, which if present, may be used by the SCP to choose one or more implementation-dependent styles of labeling. + The Barcode Value (2200,0005) and the Barcode Symbology (2200,0006), if present, may be used by the SCP to print a barcode on the label. + Note It is SCU responsibility to convey a value for the Barcode Value (2200,0005) Attribute consistent in length and content with the requested Barcode Symbology (2200,0006). +
+
+ Media Disposition + The Media Disposition (2200,0004), if present, may be used by the SCP to determine where and to whom to send the media when completed. + + For example, it may contain the name and address of a referring doctor, and be used to print a label for an envelope or mailer, or as additional material to be printed on the media label. + +
+
+ Allow Media Splitting + The SCP may or may not have the capability to split a request over more than one piece of media (e.g., if it doesn't fit on one). If it does, then the following SCP behavior shall apply and the specified Attributes are required to be supported by the SCP. + The Allow Media Splitting Attribute (2200,0007) shall be used by the SCP to determine if it is permitted to split this request over more than one piece of media. + + + + If the file-set size exceeds the media storage capacity, and this flag has been set to NO, the SCP shall refuse to process the request. + + + If the requested Media Application Profile allows for lossless compression, and images are not already compressed, such compression may be applied by the SCP in order to fit all instances on a single piece of media. This also applies to lossy compression if it has not been allowed by the value of Allow Lossy Compression (2200,000F). + + + +
+
+ Include Non-DICOM Objects + The SCP may or may not have the capability to include on the created media additional Non-DICOM objects (e.g., HTML files, JPEG images) that are a rendering of the DICOM instances. If it does, then the following SCP behavior shall apply and the specified Attributes are required to be supported by the SCP. + The Include Non-DICOM Objects (2200,0008) shall be used to request the SCP to add additional Non-DICOM objects onto the created media. + An SCP is not required to be able to add such files. Inability to add Non-DICOM objects is not an error. + If Include Non-DICOM Objects (2200,0008) is set to NO, the SCP shall not include additional non-DICOM objects on the media. +
+
+ Include Display Application + The SCP may or may not have the capability to include on the created media an application for displaying DICOM instances. If it does, then the following SCP behavior shall apply and the specified Attributes are required to be supported by the SCP. + The Include Display Application (2200,0009) shall be used to request the SCP to add an application for displaying DICOM instances onto the created media. + An SCP is not required to be able to add such an application. Inability to add a display application is not an error. + Whether the display application is capable of displaying all stored instances is beyond the scope of the standard. + Whether the display application automatically executes when media is inserted for reading is beyond the scope of the standard. + Which platforms are supported by the display application(s) is beyond the scope of the standard. + + Multiple files may need to be included in the media to support the display application, rather than a single executable file, and these may be present, even if the Include Non-DICOM Objects (2200,0008) Attribute has a value of NO. + + If Include Display Application (2200,0009) is set to NO, the SCP shall not include a display application on the media. +
+
+ Allow Lossy Compression + If Allow Lossy Compression (2200,000F) has a value of YES, the SCP is allowed to perform lossy compression under the following circumstances: + + + if it receives uncompressed or lossless compressed images yet is requested to use a profile that requires lossy compression, or + + + if Allow Media Splitting (2200,0007) is NO, and the request would otherwise need to be split across media. + + + If Allow Lossy Compression (2200,000F) has a value of YES but the requested profile does not permit lossy compression, lossy compression shall not be performed. + The level of compression is at the SCP's discretion. + The SCP shall not decompress and recompress already lossy compressed images, but may use images that have already been lossy compressed. + The SCP is never required to perform lossy compression. + If Allow Lossy Compression (2200,000F) has a value of NO, the SCP is not allowed to perform lossy compression. If Allow Lossy Compression (2200,000F) has a value of NO and the requested profile requires lossy compression, an error shall be returned. +
+
+
+ Service Class User Behavior + The SCU shall use the N-CREATE primitive to inform the SCP that a new media creation request has been placed and to convey the proprieties of this request. The request proprieties (e.g., the set of SOP Instances that the creating interchange media shall contain) are referenced in the IOD Attributes as specified in . + Upon receipt of a successful N-CREATE Response Status Code from the SCP, the SCU now knows that the SCP has received the N-CREATE request and a new media creation request has been created. + Upon receipt of a failure N-CREATE Response Status Code from the SCP, the SCU now knows that the SCP will not process the request. The actions taken by the SCU upon receiving the status is beyond the scope of this Standard. + At any time after receipt of the N-CREATE-Response, the SCU may release the association on which it sent the N-CREATE-Request. + + An N-GET of the corresponding of the Media Creation Management SOP Class may be performed on the same or subsequent associations. + +
+
+ Service Class Provider Behavior + Upon receipt of the N-CREATE request, the SCP shall return, via the N-CREATE response primitive, the N-CREATE Response Status Code applicable to the associated request. A success status conveys that the SCP has successfully received the N-CREATE request. + Warning statuses shall not be returned. + Any other status (i.e., a failure status) conveys that the SCP is not processing the media creation request. + + + + It is not specified by the Standard what checks the SCP shall accomplish after the N-CREATE request primitive reception and before returning the N-CREATE response. Implementations are discouraged from performing extended validation of the contents of the N-CREATE request, such as availability of the referenced Composite SOP Instances, support for the requested profiles, etc. In case of N-CREATE failure, the SCU would not be able to perform an N-GET to determine the detailed reasons for failure, and allow operators to apply suitable correction actions to make the request processable (e.g., resending any missing Composite SOP Instances). Such checks are better deferred until after receipt of the N-ACTION request, after which an N-GET may be performed. + + + The Standard does not require the SCP to queue multiple requests, though implementations are encouraged to do so. As a consequence, a new request before a previous request has been completed may fail immediately, or may return a successful response and be queued. The size of any such queue is beyond the scope of the Standard. + + + How long the instance of the Media Creation Management SOP Class persists once the Execution Status (2100,0020) has been set to IDLE is beyond the scope of the Standard. + + + + The N-CREATE implicitly creates the Execution Status (2100,0020) and Execution Status Info (2100,0030) Attributes, which may subsequently be retrieved by an N-GET. +
+
+ Status Codes. + The status values that are specific for this action are defined in . See for general response status codes. + + + + + + + + + + + + + + + + +
SOP Class Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Failure + + Refused because an Initiate Media Creation action has already been received for this SOP Instance. + + A510 +
+
+
+
+ Initiate Media Creation + The Initiate Media Creation operation allows an SCU to request an SCP to create Interchange Media according to an already created Media Creation Management SOP Instance. An SCP shall use this operation to schedule the creation of Interchange Media. This operation shall be invoked through the N-ACTION primitive. +
+ Action Information + The DICOM AEs that claim conformance to this SOP Class as an SCU and/or an SCP shall support the Action Types and Action Information as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + +
Media Creation Request - Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Initiate Media Creation + + 1 + + Number of Copies + + (2000,0010) + + 3/1 +
+ Request Priority + + (2200,0020) + + 3/3 + See + +
+
+ Priority + The Request Priority (2200,0020), if present, may be used by the SCP to prioritize a higher priority request over other pending lower priority requests. +
+
+
+ Service Class User Behavior + The SCU shall use the N-ACTION primitive to request the SCP to create Interchange Media according to an already created Media Creation Management SOP Instance. Action Information is specified in Table S. 3.2.2.1-1. + Upon receipt of a successful N-ACTION Response Status Code from the SCP, the SCU now knows that the SCP has received the N-ACTION Initiate Media Creation request and will process the request. + Upon receipt of a failure N-ACTION Response Status Code from the SCP, the SCU now knows that the SCP will not process the Initiate Media Creation request. The actions taken by the SCU upon receiving the status is beyond the scope of this Standard. + At any time after receipt of the N-ACTION-Response, the SCU may release the association on which it sent the N-ACTION-Request. + + + + An N-GET of the corresponding of the Media Creation Management SOP Class may be performed on the same or subsequent associations. + + + The duration for which the SOP Instance UID of an instance of the Media Creation Management SOP Class remains active once the request has been completed or has failed is implementation dependent, but should be sufficiently long to allow an SCU to determine the ultimate outcome of the request. + + + +
+
+ Service Class Provider Behavior + Upon receipt of the N-ACTION Initiate Media Creation request, the SCP shall return, via the N-ACTION response primitive, the N-ACTION Response Status Code applicable to the associated request. A success status conveys that the SCP has successfully scheduled the request. + + + + The extent of validation of the contents of the request, the availability of the referenced Composite SOP Instances, support for the requested profiles and other checks that may determine the ultimate success or failure of the request are not specified by the Standard. In particular, a request may be immediately accepted successfully, but subsequently fail for some reason, or the N-ACTION response primitive may contain a status that reflects a more thorough (and prolonged) check. + + + How long any Composite Instances that have been transferred via the Storage Service Class to the SCP for the purpose of a Media Creation Request persist, is beyond the scope of the Standard. The Preserve Composite Instances After Media Creation (2200,000A) flag is provided as a hint only. Even if this flag is set, a subsequent request referencing some or all of the same instances may fail if the SCP had reason to flush its cache of instances in the interim, and the SCU may need to be prepared to re-send them. + + + How long the instance of the Media Creation Management SOP Class persists once the Execution Status (2100,0020) has been set to DONE or FAILED is beyond the scope of the Standard. + + + + The N-ACTION implicitly creates or updates the Execution Status (2100,0020), Execution Status Info (2100,0030), Total Number of Pieces of Media Created (2200,000B), Failed SOP Sequence (0008,1198) and Referenced Storage Media Sequence (2200,000D) Attributes, which may subsequently be retrieved by an N-GET. +
+
+ Status Codes + There are no specific status codes. See for response status codes. +
+
+
+ Cancel Media Creation + The Cancel Media Creation operation allows an SCU to request an SCP to cancel a media creation request, whether or not it has begun to be processed. This operation shall be invoked through the N-ACTION primitive. +
+ Action Information + The DICOM AEs that claim conformance to this SOP Class as an SCU and/or an SCP shall support the Action Types and Action Information as specified in . + + + + + + + + + + + + + + + + + +
Media Creation Request - Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Cancel Media Creation + + 2 + + + +
+
+
+ Service Class User Behavior + The SCU shall use the N-ACTION primitive to request the SCP to cancel the media creation request corresponding to the Affected SOP Instance UID in the N-ACTION request primitive, whether or not it has been initiated with an N-ACTION Initiate Media Creation request, and whether or not it has begun to be processed (i.e., is pending or in progress). + Upon receipt of a successful N-ACTION Response Status Code from the SCP, the SCU knows that the SCP has received the N-ACTION Cancel Media Creation request, has canceled any pending or in progress media creation, and deleted the Media Creation Management SOP Instance. + + Successful cancellation implies that a subsequent N-GET of the corresponding Media Creation Management SOP Instance would fail. + + Upon receipt of a failure N-ACTION Response Status Code from the SCP, the SCU knows that the SCP will not process the Cancel Media Creation request. The actions taken by the SCU upon receiving the status is beyond the scope of this Standard. + + Cancellation failure implies that media creation has already completed (successfully or not), or will proceed. The status of the media creation request may still be obtained with an N-GET, unless the reason for failure was that the SOP Instance did not exist. + +
+
+ Service Class Provider Behavior + Upon receipt of the N-ACTION Cancel Media Creation request, the SCP shall return, via the N-ACTION response primitive, the N-ACTION Response Status Code applicable to the associated request. A success status conveys that the SCP has successfully canceled the request. + A failure status conveys that the SCP has failed to cancel the request, in which case the Execution Status (2100,0020), Execution Status Info (2100,0030), Total Number of Pieces of Media Created (2200,000B), Failed SOP Sequence (0008,1198) and Referenced Storage Media Sequence (2200,000D) Attributes may subsequently be retrieved by an N-GET. +
+
+ Status Codes + The status values that are specific for this SOP Class and DIMSE Service are defined in .See for general response status codes. + + + + + + + + + + + + + + + + + + + + + + + + +
Response Statuses
+ + Service Status + + + + Further Meaning + + + + Response Status Codes + +
+ Failure + + Media creation request already completed. + + C201 +
+ Media creation request already in progress and cannot be interrupted. + + C202 +
+ Cancellation denied for unspecified reason. + + C203 +
+
+
+
+ Get Media Creation Result + The Get Media Creation Result operation allows an SCU to request of an SCP the status of a media creation request. This operation shall be invoked through the N-GET primitive used in conjunction with the appropriate Media Creation Management SOP Instance corresponding to the creation request. +
+ Attributes + The Application Entity that claims conformance to this SOP Class as an SCU may choose to interpret the Attributes maintained by the SCP that the SCU receives via the operations of the SOP Class. The Application Entity that claims conformance as an SCP to this SOP Class shall support the Attributes specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Media Creation Management SOP Class N-GET Attributes
+ + Attribute Name + + + + Tag + + + + Requirement Type (SCU/SCP) + +
+ Specific Character Set + + (0008,0005) + + 3/1C + (Required if expanded or replacement character set is used) +
+ Execution Status + + (2100,0020) + + 3/1 +
+ Execution Status Info + + (2100,0030) + + 3/1 +
+ Total Number of Pieces of Media Created + + (2200,000B) + + 3/1 +
+ Failed SOP Sequence + + (0008,1198) + + 3/2 +
+ Referenced Storage Media Sequence + + (2200,000D) + + 3/2 +
+ + All Other Attributes of the + + + + + 3/3 +
+
+
+ Service Class User + The SCU shall specify in the N-GET request primitive the UID of the Media Creation Management SOP Instance for which Attribute Values are to be returned. The SCU shall be permitted to request that Attribute Values be returned for any Media Creation Management SOP Class Attribute specified in . Additionally, values may be requested for optional Media Creation Management Module Attributes. + The SCU shall specify the list of Media Creation Management SOP Class Attributes for which the Attribute Values are to be returned. The encoding rules for this list are specified in the N-GET request primitive specified in . + In an N-GET operation, Sequence Attributes can only be requested in their entirety, and only the top level Sequence Attribute can be included in the request. + The SCU shall be capable of receiving all requested Attribute Values provided by the SCP in response to the N-GET indication primitive. The SCU may request Attribute values for optional Attributes that are not maintained by the SCP. In such a case the SCU shall function properly regardless of whether the SCP returns values for those Attributes or not. This Service Class Specification places no requirements on what the SCU shall do as a result of receiving this information. + + In order to interpret accurately the character set used for Attribute values returned, it is recommended that the Attribute value for Specific Character Set (0008,0005) be requested in the N-GET request primitive. + +
+
+ Service Class Provider + This operation allows the SCU to request from the SCP, selected Attribute Values for a specific Media Creation Management SOP Instance. This operation shall be invoked through the use of the DIMSE N-GET Service used in conjunction with the appropriate Media Creation Management SOP Instance. + The SCP shall return, via the N-GET response primitive, the N-GET Response Status Code applicable to the associated request. Contingent on the N-GET Response Status, the SCP shall return, via the N-GET Response Primitive, Attribute Values for all requested Attributes maintained by the SCP (see ). The SCP shall not return Data Elements for optional Attributes that are not maintained by the SCP. + The SCP shall return the entire content of a Sequence if a Sequence Attribute is requested. +
+
+ Status Codes + The status values that are specific for this SOP Class and DIMSE Service are defined in . + See for response status codes. + + + + + + + + + + + + + + + + +
Response Statuses
+ + Service Status + + + + Further Meaning + + + + Response Status Codes + +
+ Warning + + Requested optional Attributes are not supported + + 0001 +
+
+
+
+
+ Media Creation Management SOP Class UID + The Media Creation Management SOP Class shall be uniquely identified by the Media Creation Management SOP Class UID, which shall have the value "1.2.840.10008.5.1.1.33". +
+
+
+ Conformance Requirements + Implementations claiming Standard SOP Class Conformance to the Media Creation Management SOP Class shall be conformant as described in this Section and shall include within their Conformance Statement information as described in this Section and sub-Sections. + An implementation may claim conformance to this SOP Class as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that is conformant to this SOP Class as an SCU shall meet conformance requirements for + + + the operations and actions that it invokes + + + The mechanisms used by the SCU to transfer SOP Instances to the SCP using the Storage Service Class prior to initiating a request operation shall also be documented, and in particular the Transfer Syntaxes that may be proposed. +
+ Operations + The SCU shall document in the Conformance Statement the actions and behavior that cause the SCU to generate an N-CREATE primitive (Create Media Creation Request), an N-ACTION primitive (Initiate Media Creation and Cancel Media Creation) or an N-GET primitive (Get Media Creation Result). + The SCU shall specify the SOP Class UIDs for which it may request media creation. + The SCU shall specify the Media Application Profiles for which it may request media creation. + The SCU shall specify if it supports the optional Storage Media File-Set ID & UID Attributes in the N-CREATE. + The SCU shall specify if it supports the optional Icon Image Sequence Attributes in the N-CREATE. + The SCU shall describe its use of expanded or replacement character sets, both in the N-CREATE, the N-GET and in its use of the Storage Service Class for composite instances. + The SCU shall specify whether or not it retries failed requests. + + This allows the reader of a Conformance Statement to determine whether or not human intervention will be needed in the event of transient failures, or whether the SCU may be able to recover automatically. + + The Conformance Statement shall be formatted as defined in + +
+
+
+ SCP Conformance + An implementation that is conformant to this SOP Class as an SCP shall meet conformance requirements for + + + the operations and actions that it performs + + + The Storage Service Class mechanisms accepted by the SCP prior to receiving a request operation shall also be documented, and in particular the Transfer Syntaxes that may be accepted. +
+ Operations + The SCP shall document in the Conformance Statement the behavior and actions of the SCP upon receiving the N-CREATE primitive (Create Media Creation Request), N-ACTION primitive (Initiate Media Creation and Cancel Media Creation) or the N-GET primitive (Get Media Creation Result). + The SCP shall specify the SOP Class UIDs for which it will accept media creation requests. + The SCP shall specify the Media Application Profiles for which it will accept media creation requests, and what default profiles it will use in the event that they are not specified by the SCU. + + The forms of media that can be created are implicit in the list of Media Application Profiles supported, each of which is media-specific. + + The SCP shall specify whether or not it supports creation of optional Icon Image Sequence Attributes in the DICOMDIR if none are supplied by the SCU. + The SCP shall specify the manner of use of label information, and in particular which: + + + Attributes are extracted from the Composite Instances when so instructed + + + barcode symbologies - if any - are supported + + + The SCP shall describe its use of expanded or replacement character sets, both in the N-CREATE, the N-GET and in its extraction of information from the Composite Instances for incorporation in the DICOMDIR and on the media label. The SCP shall describe its use of the Attributes both in the N-CREATE, and N-ACTION and the Composite Instances to create the media label. + The SCP shall specify if and how it supports the following optional Attributes in the N-CREATE and N-ACTION: + + + Storage Media File-Set ID (0088,0130) & Storage Media File-Set UID (0088,0140) + + + Media Disposition (2200,0004) + + + Priority (2000,0020) + + + Preserve Composite Instances After Media Creation (2200,000A) + + + The SCP shall specify the duration of persistence of received Composite Instances after a request has been processed successfully or unsuccessfully. + The SCP shall specify how long it will maintain: + + + the result of the creation of media after the request has succeeded or failed + + + the Media Creation Management Instances whose status is IDLE. + + + The SCP shall specify the action taken when a permanent failure (e.g., a media writing failure) or a transient failure (e.g., no empty media available) occurs, and their relationship with the media creation request status transaction. + + For example, how many times the SCP will retry writing a new piece of media before setting the Execution Status (2100,0020) to FAILURE, how many media creation requests the SCP is able to queue, the SCP behavior when the request queue, if any, is full. + + The SCP shall specify if it is able to split a media creation request over more than one piece of media, if the file-set doesn't fit on one. + The SCP shall specify if it is able to add to the created media Non-DICOM objects (e.g., html files, JPEG images), how these objects are organized, and how it interprets the Include Non-DICOM Objects (2200,0008) Attribute. + The SCP shall specify if it is able to add to the created media DICOM display applications, and how it interprets the Include Display Application (2200,0009) Attribute. + The Conformance Statement shall be formatted as defined in . +
+
+
+
+ + Hanging Protocol Storage Service Class + See . + + The requirements of this section have been consolidated into the Non-Patient Object Storage Service Class (see ). + + + + Hanging Protocol Query/Retrieve Service Class +
+ Overview +
+ Scope + The Hanging Protocol Query/Retrieve Service Class defines an application-level class-of-service that facilitates access to Hanging Protocol composite objects. It provides query and retrieve/transfer capabilities similar to the Basic Worklist Management Service Class and Query/Retrieve Service Class. +
+
+ Conventions + See Conventions for the Basic Worklist Management Service (K.1.2). +
+
+ Query/Retrieve Information Model + In order to serve as an SCP of the Hanging Protocol Query/Retrieve Service Class, a DICOM AE possesses information about the Attributes of a number of Hanging Protocol composite SOP Instances. The information is organized into a Hanging Protocol Information Model. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Hanging Protocol Query/Retrieve Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Hanging Protocol Query/Retrieve Service Class are implemented using the DIMSE-C C-FIND, C-MOVE and C-GET services as defined in . + The semantics of the C-FIND and C-GET services are the same as those defined in the Service Definition of the Basic Worklist Management Service Class. + The semantics of the C-MOVE service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. +
+
+
+ Hanging Protocol Information Model Definition + The Hanging Protocol Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + The Hanging Protocol Information Model is defined, with the Entity-Relationship Model Definition and Key Attributes Definition analogous to those defined in the Worklist Information Model Definition of the Basic Worklist Management Service. +
+
+ Hanging Protocol Information Model + The Hanging Protocol Information Model is based upon a one level entity: + + + Hanging Protocol object instance + + + The Hanging Protocol object instance contains Attributes associated with the Hanging Protocol object IE of the Composite IODs as defined in . +
+
+ DIMSE-C Service Groups +
+ C-FIND Operation + See the C-FIND Operation definition for the Basic Worklist Management Service Class (K.4.1), and substitute "Hanging Protocol" for "Worklist. The "Worklist" Search Method shall be used. + The SOP Class UID identifies the Hanging Protocol Information Model against which the C-FIND is to be performed. The Key Attributes and values allowable for the query are defined in the SOP Class definition for the Hanging Protocol Information Model. +
+
+ C-MOVE Operation + See the C-MOVE Operation definition for the Query/Retrieve Service Class (C.4.2). No Extended Behavior or Relational-Retrieve is defined for the Hanging Protocol Query/Retrieve Service Class. + Query/Retrieve Level (0008,0052) is not relevant to the Hanging Protocol Query/Retrieve Service Class, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+ C-GET Operation + See the C-GET Operation definition for the Query/Retrieve Service Class (C.4.3). No Extended Behavior or Relational-Retrieve is defined for the Hanging Protocol Query/Retrieve Service Class. + Query/Retrieve Level (0008,0052) is not relevant to the Hanging Protocol Query/Retrieve Service Class, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+
+ Association Negotiation + See the Association Negotiation definition for the Basic Worklist Management Service Class (K.5). +
+
+ SOP Class Definitions +
+ Hanging Protocol Information Model +
+ E/R Model + The Hanging Protocol Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Hanging Protocol Instance. + +
+ Hanging Protocol Information Model E/R Diagram + + + + + + +
+
+
+
+ Hanging Protocol Attributes + + defines the Attributes of the Hanging Protocol Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Hanging Protocol Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + +
+ + Hanging Protocol Definition + +
+ Hanging Protocol Name + + (0072,0002) + + R + + 1 + + This Attribute shall be retrieved with Single Value, Wild Card or Universal matching. +
+ Hanging Protocol Description + + (0072,0004) + + - + + 1 + +
+ Hanging Protocol Level + + (0072,0006) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ Hanging Protocol Creator + + (0072,0008) + + - + + 1 + +
+ Hanging Protocol Creation DateTime + + (0072,000A) + + - + + 1 + +
+ Hanging Protocol Definition Sequence + + (0072,000C) + + R + + 1 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Modality + + (0008,0060) + + R + + 2 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Anatomic Region Sequence + + (0008,2218) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >> Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >> Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >Laterality + + (0020,0060) + + R + + 2 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ > Procedure Code Sequence + + (0008,1032) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >> Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >> Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >Reason for Requested Procedure Code Sequence + + (0040,100A) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >> Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >> Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ Number of Priors Referenced + + (0072,0014) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ Hanging Protocol User Identification Code Sequence + + (0072,000E) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Hanging Protocol User Group Name + + (0072,0010) + + R + + 3 + +
+ + Hanging Protocol Environment + +
+ Number of Screens + + (0072,0100) + + R + + 2 + +
+ Nominal Screen Definition Sequence + + (0072,0102) + + - + + 2 + +
+ >Number of Vertical Pixels + + (0072,0104) + + - + + 1 + +
+ >Number of Horizontal Pixels + + (0072,0106) + + - + + 1 + +
+ >Display Environment Spatial Position + + (0072,0108) + + - + + 1 + +
+ >Screen Minimum Grayscale Bit Depth + + (0072,010A) + + - + + 1C + + Required if Screen Minimum Color Bit Depth (0072,010C) is not present. +
+ >Screen Minimum Color Bit Depth + + (0072,010C) + + - + + 1C + + Required if Screen Minimum Grayscale Bit Depth (0072,010A) is not present. +
+ >Application Maximum Repaint Time + + (0072,010E) + + - + + 3 + +
+
+
+ Conformance Requirements + An implementation may conform to one of the Hanging Protocol Information Model SOP Classes as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes shall support queries against the Hanging Protocol Information Model using the C-FIND SCU Behavior described for the Basic Worklist Management Service Class (see and ). + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCU shall state in its Conformance Statement whether it requests Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCU shall support transfers against the Hanging Protocol Information Model using the C-MOVE SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+ C-GET SCU Conformance + An implementation that conforms to the Hanging Protocol Information Model - GET SOP Class as an SCU shall support transfers against the Hanging Protocol Information Model using the C-GET SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCP shall support queries against the Hanging Protocol Information Model using the C-FIND SCP Behavior described for the Basic Worklist Management Service Class (see ). + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCP shall state in its Conformance Statement whether it supports Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching and encoding responses. +
+
+ C-MOVE SCP Conformance + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCP shall support transfers against the Hanging Protocol Information Model using the C-MOVE SCP baseline behavior described for the Query/Retrieve Service Class (see ). + An implementation that conforms to one of the Hanging Protocol Information Model SOP Classes as an SCP, which generates transfers using the C-MOVE operation, shall state in its Conformance Statement the Hanging Protocol Storage Service Class SOP Class under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to the Hanging Protocol Information Model - GET SOP Class as an SCP shall support transfers against the Hanging Protocol Information Model using the C-GET SCP baseline behavior described for the Query/Retrieve Service Class (see ). + An implementation that conforms to the Hanging Protocol Information Model - GET SOP Class as an SCP, which generates transfers using the C-GET operation, shall state in its Conformance Statement the Hanging Protocol Storage Service Class SOP Class under which it will support the C-STORE sub-operations generated by the C-GET. +
+
+
+
+ SOP Classes + The SOP Classes of the Hanging Protocol Information Model in the Hanging Protocol Query/Retrieve Service Class identify the Hanging Protocol Information Model, and the DIMSE-C operations supported. The following Standard SOP Classes are identified: + + + + + + + + + + + + + + + + + + + + + + +
Hanging Protocol SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Hanging Protocol Information Model - FIND + + 1.2.840.10008.5.1.4.38.2 +
+ Hanging Protocol Information Model - MOVE + + 1.2.840.10008.5.1.4.38.3 +
+ Hanging Protocol Information Model - GET + + 1.2.840.10008.5.1.4.38.4 +
+
+
+
+
+ + Substance Administration Query Service Class (Normative) +
+ Overview +
+ Scope + The Substance Administration Query Service Class defines an application-level class-of-service that facilitates obtaining detailed information about substances or devices used in imaging, image-guided treatment, and related procedures. It also facilitates obtaining approval for the administration of a specific contrast agent or drug to a specific patient. + This Service Class is intended as part of a larger workflow that addresses patient safety in the imaging environment. This Service addresses only the communication protocol that allows a point of care device (imaging modality) to interrogate an SCP Application for information about an administered substance, or for verification of appropriateness of the substance for the patient. The SCP Application uses patient safety related data, such as allergies, current medications, appropriate dosages, patient condition indicated by lab results, etc., to respond to the queries; however, the mechanism of such use is beyond the scope of this Standard. How the point of care device uses the responses to the queries, e.g., by display to a user, or by locking of certain device functions, is also beyond the scope of this Standard. + + + + The SCP of this Service Class is not necessarily a clinical decision support (CDS) system, but may be a gateway system between this DICOM Service and an HL7 or proprietary interface of a CDS system. Such implementation design is beyond the scope of the DICOM standard. + + + The Service will result in a Query response containing zero or one items. However, to facilitate implementation, the Service uses the general query mechanism supporting multiple item responses, as used in other DICOM query service classes. + + + +
+
+ Conventions + Key Attributes serve two purposes; they may be used as Matching Key Attributes and Return Key Attributes. Matching Key Attributes may be used for matching (criteria to be used in the C-FIND request to determine whether an entity matches the query). Return Key Attributes may be used to specify desired return Attributes (what elements in addition to the Matching Key Attributes have to be returned in the C-FIND response). + Matching Key Attributes may be of Type "required" (R) or "optional" (O). Return Key Attributes may be of Type 1, 1C, 2, 2C, 3 as defined in . +
+
+ Substance Administration Query Information Model + In order to serve as a Service Class Provider (SCP) of the Substance Administration Query Service Class, a DICOM Application Entity (AE) must be able to return information about the Attributes of a substance, device, or a substance administration act. This information is organized into well defined Substance Administration Query Information Models. + A specific SOP Class of the Substance Administration Query Service Class consists of an informative Overview, an Information Model Definition and a DIMSE-C Service Group. In this Service Class, the Information Model plays a role similar to an Information Object Definition (IOD) of other DICOM Service Classes. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Substance Administration Query Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Substance Administration Query Service Class are implemented using the DIMSE-C C-FIND service as defined in . + Only a baseline behavior of the DIMSE-C C-FIND is used in the Service Class. Extended negotiation is not used. + The following description of the DIMSE-C C-FIND service provides a brief overview of the SCU/SCP semantics. + A C-FIND service conveys the following semantics: + + + The SCU requests that the SCP perform a match for the Matching Keys and return values for the Return Keys that have been specified in the Identifier of the request, against the information that the SCP possesses relating to the Information Model specified in the SOP Class. + + + + In this Annex, the term "Identifier" refers to the Identifier service parameter of the C-FIND service as defined in . + + + + The SCP generates at most one C-FIND response for a match with an Identifier containing the values of all Matching Key Attributes and all known Return Key Attributes requested. This response shall contain a status of Pending. + + + When the process of matching is complete, with zero or one match, a C-FIND response is sent with a status of Success and no Identifier. + + + A Failure response to a C-FIND request indicates that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-CANCEL-FIND request at any time during the processing of the C-FIND service. The SCP will interrupt all matching and return a status of Canceled. + + + + The SCU needs to be prepared to receive C-FIND responses sent by the SCP until the SCP finally processed the C-CANCEL-FIND request. + +
+
+
+ Substance Administration Query Information Model Definition + The Substance Administration Query Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + Information Model Definitions for standard SOP Classes of the Substance Administration Query Service Class are defined in this Annex. A Substance Administration Query Information Model Definition contains: + + + an Entity-Relationship Model Definition + + + a Key Attributes Definition. + + +
+ Entity-Relationship Model Definition + Substance Administration Query Information Models consist of a single level that includes all Matching Key Attributes and all Return Key Attributes that may be sent from the SCU to the SCP in the request, and whose values are expected to be returned from the SCP to the SCU in the response (or Query items). The Matching Key Attribute values in the request specify the Query items that are to be returned in the response. All Key Attributes (the Matching Key Attributes and the Return Key Attributes) in the request determine which Attribute values are returned in the response for that Query. +
+
+ Attributes Definition + Attributes are defined for each entity in the internal Entity-Relationship Model. An Identifier in a C-FIND request shall contain values to be matched against the Attributes of the Entities in a Substance Administration Query Information Model. For any Query request, the set of entities for which Attributes are returned shall be determined by the set of Matching and Return Key Attributes specified in the Identifier. +
+ Attribute Types + All Attributes of entities in a Substance Administration Query Information Model shall be specified both as a Matching Key Attribute (either required or optional) and as a Return Key Attribute. +
+ Matching Key Attributes + The Matching Key Attributes are Keys, which select Query items to be included in a requested Query. +
+ Required Matching Key Attributes + A Substance Administration Query Service SCP shall support matching based on values of all Required Matching Key Attributes of the C-FIND request. +
+
+ Optional Matching Key Attributes + In the Substance Administration Query Information Model, a set of Attributes may be defined as Optional Matching Key Attributes. Optional Matching Key Attributes contained in the Identifier of a C-FIND request may induce two different types of behavior depending on support for matching by the SCP. If the SCP + + + does not support matching on the Optional Matching Key Attribute, then the Optional Matching Key Attribute shall be ignored for matching but shall be processed in the same manner as a Return Key Attribute. + + + supports matching of the Optional Matching Key Attribute, then the Optional Matching Key Attribute shall be processed in the same manner as a Required Matching Key. + + + + + + The Conformance Statement of the SCP lists the Optional Matching Key Attributes that are supported for matching. + + + An SCU can not expect the SCP to support a match on an Optional Matching Key. + + + +
+
+
+ Return Key Attributes + The values of Return Key Attributes to be retrieved with the Query are specified with zero-length (universal matching) in the C-FIND request. SCPs shall support Return Key Attributes defined by a Substance Administration Query Information Model according to the Data Element Type (1, 1C, 2, 2C, 3) as defined in . + Every Matching Key Attribute shall also be considered as a Return Key Attribute. Therefore the C-FIND response shall contain, in addition to the values of the requested Return Key Attributes, the values of the requested Matching Key Attributes. + + + + The Conformance Statement of the SCP lists the Return Key Attributes of Type 3 that are supported. + + + An SCU may choose to supply any subset of Return Key Attributes. + + + An SCU can not expect to receive any Type 3 Return Key Attributes. + + + Return Key Attributes with VR of SQ may be specified either with zero-length, or with a zero-length item in the sequence. + + + +
+
+
+ Attribute Matching + The following types of matching, which are defined by the Query/Retrieve Service Class in , may be performed on Matching Key Attributes in the Substance Administration Query Service Class. Different Matching Key Attributes may be subject for different matching types. The Substance Administration Query Information Model defines the type of matching for each Required Matching Key Attribute. The Conformance Statement of the SCP shall define the type of matching for each Optional Matching Key Attribute. The types of matching are: + + + Single Value Matching + + + Sequence Matching + + + The following type of matching, which is defined by the Query/Retrieve Service Class in of this Part, shall be performed on Return Key Attributes in the Substance Administration Query Service Class. + + + Universal Matching + + + See and subsections for specific rules governing of Matching Key Attribute encoding for and performing of different types of matching. + The Specific Character Set (0008,0005) Attribute may be present in the Identifier but is never matched, i.e., it is not considered a Matching Key Attribute. See for details. +
+
+
+
+ Query Information Models + Each Substance Administration Query Information Model is associated with one SOP Class. The following Substance Administration Query Information Models are defined: + + + Product Characteristics Query Information Model + + + Substance Approval Query Information Model + + +
+
+ DIMSE-C Service Group + One DIMSE-C Service is used in the construction of SOP Classes of the Substance Administration Query Service Class. The following DIMSE-C operation is used. + + + C-FIND + + +
+ C-FIND Operation + SCPs of SOP Classes of the Substance Administration Query Service Class are capable of processing queries using the C-FIND operation as described in . The C-FIND operation is the mechanism by which queries are performed. Matches against the keys present in the Identifier are returned in C-FIND responses. +
+ C-FIND Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Substance Administration Query Information Model against which the C-FIND is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-FIND operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-FIND operation with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. +
+
+ Identifier + Both the C-FIND request and response contain an Identifier encoded as a Data Set (see ). +
+ Request Identifier Structure + An Identifier in a C-FIND request shall contain + + + Key Attributes values to be matched against the values of Attributes specified in that SOP Class. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Request Identifier. It shall not be included otherwise. + + + + This means that Specific Character Set (0008,0005) is included if the SCU supports expanded or replacement character sets in the context of this service. It will not be included if expanded or replacement character sets are not supported by the SCU. + + The Key Attributes and values allowable for the query shall be defined in the SOP Class definition for the corresponding Substance Administration Query Information Model. +
+
+ Response Identifier Structure + The C-FIND response shall not contain Attributes that were not in the request or specified in this section. + An Identifier in a C-FIND response shall contain: + + + Key Attributes with values corresponding to Key Attributes contained in the Identifier of the request (Key Attributes as defined in .) + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Response Identifier. It shall not be included otherwise. The C-FIND SCP is not required to return responses in the Specific Character Set requested by the SCU if that character set is not supported by the SCP. The SCP may return responses with a different Specific Character Set. + + + + This means that Specific Character Set (0008,0005) is included if the SCP supports expanded or replacement character sets in the context of this service. It will not be included if expanded or replacement character sets are not supported by the SCP. + + + + Conditionally, the Attribute HL7 Structured Document Reference Sequence (0040,A390) and its subsidiary Sequence Items as specified in the SOP Common Module (see ). This Attribute shall be included if HL7 Structured Documents are referenced within the Identifier, e.g., in the Pertinent Documents Sequence (0038,0100). + + +
+
+
+ Status + + defines the status code values that might be returned in a C-FIND response. Fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-FIND Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources + + A700 + + (0000,0902) +
+ Identifier Does Not Match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ Unable to process + + Cxxx + (values C000 through CFFF as assigned by the implementation) + + (0000,0901) + (0000,0902) +
+ Cancel + + Matching terminated due to Cancel request + + FE00 + + None +
+ Success + + Matching is complete - No final Identifier is supplied. + + 0000 + + None +
+ Pending + + Matches are continuing - Current Match is supplied and any Optional Keys were supported in the same manner as Required Keys. + + FF00 + + Identifier +
+ Matches are continuing - Warning that one or more Optional Keys were not supported for existence for this Identifier. + + FF01 + + Identifier +
+ + Status Codes are returned in DIMSE response messages (see ). The code values stated in column "Status Codes" are returned in Status Command Element (0000,0900). + +
+
+
+ C-FIND SCU Behavior + All C-FIND SCUs shall be capable of generating query requests that meet the requirements of the Query Search Method (see ). + Required Keys and Optional Keys associated with the Query may be contained in the Identifier. + An SCU conveys the following semantics using the C-FIND requests and responses: + + + The SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information it possesses of the Query specified in the request. + + + The SCU shall interpret Pending responses to convey the Attributes of a match of an item. + + + The SCU shall interpret a response with a status equal to Success, Failure, or Cancel to convey the end of Pending responses. + + + The SCU shall interpret a Failure response to a C-FIND request as an indication that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-FIND-CANCEL request at any time during the processing of the C-FIND. The SCU shall recognize a status of Cancel to indicate that the C-FIND-CANCEL was successful. + + +
+
+ C-FIND SCP Behavior + All C-FIND SCPs shall be capable of processing queries that meet the requirements of the Query Search (see ). + An SCP conveys the following semantics using the C-FIND requests and responses: + + + The SCP is requested to perform a match of all the keys specified in the Identifier of the request, against the information it possesses. Attribute matching is performed using the key values specified in the Identifier of the C-FIND request as defined in . + + + The SCP generates at most one C-FIND response for a match using the "Query" Search method. Such a response shall contain an Identifier whose Attributes contain values from the match. The response shall contain a status of Pending. + + + When matching is complete and any match has been sent, the SCP generates a C-FIND response that contains a status of Success. A status of Success shall indicate that a response has been sent for any match known to the SCP. + + + + + + No Identifier is contained in a response with a status of Success. For a complete definition, see . + + + When there are no matches, then no responses with a status of Pending are sent, only a single response with a status of Success. + + + + + + The SCP shall generate a response with a status of Failure if it is unable to process the request. A Failure response shall contain no Identifier. + + + If the SCP receives a C-FIND-CANCEL indication before it has completed the processing of the matches it shall interrupt the matching process and return a status of Cancel. + + +
+ Query Search Method + The following procedure is used to generate matches. + The key match Attributes contained in the Identifier of the C-FIND request are matched against the values of the Key Attributes for each Query entity. For each entity for which the Attributes match all of the specified match Attributes, construct an Identifier. This Identifier shall contain all of the values of the Attributes for this entity that match those in the C-FIND request. Return a response for each such Identifier. If there are no matching keys, then there are no matches; return a response with a status equal to Success and with no Identifier. +
+
+
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation procedure specified in shall be used to negotiate the supported SOP Classes. + Support for the SCP/SCU role selection negotiation is optional. The SOP Class Extended Negotiation is not used by this Service Class. +
+
+ SOP Class Definitions +
+ Product Characteristics Query SOP Class +
+ Product Characteristics Query SOP Class Overview + The Product Characteristics Query SOP class defines an application-level class of service that facilitates the communication of detailed information about drugs, contrast agents, or devices identified by a bar code or similar identifier. The detailed information is intended to be used both for automated processing and for presentation to a system operator. + The Product Characteristics Query SOP class supports the following example use cases: + + + Obtain the active ingredient, concentration, or other parameters of a contrast agent for inclusion in the image SOP Instances created during use of the agent, or for setting up image acquisition parameters (e.g., ultrasound transducer frequency) + + + Obtain the size parameters of a device (e.g., a catheter) for use in calibrating images that show that device + + + Obtain a network reference for an online copy of the "product label" (regulated prescribing and use data) for a drug, contrast agent, or device. + + +
+
+ Product Characteristics Query Information Model +
+ E/R Model + The Product Characteristics Query Information Model is represented by the Entity Relationship diagram shown in figure -1. + +
+ Product Characteristics E-R Diagram + + + + + + +
+
+ There is only one Information Entity in the model, which is the Product. The Attributes of a Product can be found in the following Module in . + + + Product Characteristics Module + + +
+
+ Product Characteristics Query Attributes + + defines the Attributes of the Product Characteristics Query Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Product Characteristics Query Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Product Package Identifier + + (0044,0001) + + R + + 1 + + Shall be retrieved with Single Value Matching only. +
+ Product Type Code Sequence + + (0044,0007) + + - + + 1 + +
+ >Code Value + + (0008,0100) + + - + + 1 + +
+ >Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Product Name + + (0044,0008) + + - + + 1 + +
+ Product Expiration DateTime + + (0044,000B) + + - + + 2 + +
+ Product Parameter Sequence + + (0044,0013) + + - + + 2 + +
+ >Value Type + + (0040,A040) + + - + + 1 + +
+ >Concept Name Code Sequence + + (0040,A043) + + - + + 1 + +
+ >>Code Value + + (0008,0100) + + - + + 1 + +
+ >>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ + >All other Attributes of the Product Parameter Sequence + + + + - + + 1C + + Conditional on value of Value Type (0040,A040); See Content Item Macro. +
+ + All other Attributes of the + + + + + - + + 3 + +
+ The Product Package Identifier (0044,0001) might not be globally unique and might conflict with other identifiers used within the scope of the institution. + + The package identifiers are typically unique within the scope of the substance administration management systems. This is a warning that they are not UIDs. + +
+
+
+ Conformance Requirements + An implementation may conform to the Product Characteristics Query SOP Class as an SCU or an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that conforms to the Product Characteristics Query SOP Class shall support queries against the Information Model described in using the baseline C-FIND SCU Behavior described in . + An implementation that conforms to the Product Characteristics Query SOP Class as an SCU shall state in its Conformance Statement the Return Key Attributes it requests, and how those Attributes are used in the application. + An implementation that conforms to the Product Characteristics Query SOP Class as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ SCP Conformance + An implementation that conforms to the Product Characteristics Query SOP Class shall support queries against the Product Characteristics Query Information Model described in using the C-FIND SCP Behavior described in . + An implementation that conforms to the Product Characteristics Query SOP Class as an SCP shall state in its Conformance Statement the Return Key Attributes that it supports. + An implementation that conforms to the Product Characteristics Query SOP Class as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding responses. +
+
+
+ SOP Class + The Product Characteristics Query SOP Class in the Substance Administration Service Class identifies the Product Characteristics Query Information Model, and the DIMSE-C operations supported. The following Standard SOP Class is identified: + + + + + + + + + + + + + + +
Product Characteristics Query SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Product Characteristics Query Information Model - FIND + + 1.2.840.10008.5.1.4.41 +
+
+
+
+ Substance Approval Query SOP Class +
+ Substance Approval Query SOP Class Overview + The Substance Approval Query SOP Class defines an application-level class of service that allows a device at the point of care to obtain verification of the appropriateness of contrast agents and other drugs administered during a procedure, based on the substance label barcode and the patient ID. The response is an authorization to proceed, or a warning, or a contra-indication for presentation to the system operator. + The Substance Approval Query SOP class supports the following example use cases: + + + Obtain verification that administration of a specific drug or contrast agent for an image acquisition is appropriate for the patient + + + Obtain verification that the implantation of a specific device under imaging guidance is appropriate for the patient + + + The Substance Approval Query SOP Class does not specify the mechanism used by the SCP to verify such appropriateness of administration (e.g., by comparison to allergy information in the patient's electronic health record). The duration of validity of an approval beyond the time of the response is not defined by the Standard. +
+
+ Substance Approval Query Information Model +
+ E/R Model + The Substance Approval Query Information Model is represented by the Entity Relationship diagram shown in . + +
+ Substance Approval E-R Diagram + + + + + + +
+
+ + + + The Attributes of the Information Entities can be found in the following Modules in . + + + Patient Identification Module + + + Patient Demographics Module + + + Visit Identification Module + + + Substance Administration Module + + + Substance Approval Module + + + Product Characteristics Module + + + Only selected Attributes of these Modules are used in the Substance Approval Query Information Model. + The Information Model is used in a bottom-up manner in the query; i.e., given a Product and a Patient, or alternatively a Product and a Visit, for a proposed Substance Administration act at the current time, find the Approval. + The Visit IE is included in the Information Model to support those institutions that identify patients (e.g., on a bar coded wristband) by Admission ID (i.e., the ID of the Visit), rather than Patient ID. This allows automation of query construction using a scan of the Admission ID. The Admission ID can be mapped to the Patient ID by the SCP for the purpose of the performing the query matching. + + + + The Visit is identified by the Admission ID (0038,0010) Attribute, but in the "Model of the Real World for the Purpose of Modality-IS Interface" (see ), the Visit is subsidiary to the Patient; hence the Admission ID may only be unique within the context of the patient, not within the context of the institution. The use of the Admission ID Attribute to identify the Visit (and hence the Patient) is only effective if the Admission ID is unique within the context of the institution. + + + Certain institutions, e.g., ambulatory imaging centers that do not "admit" patients, may use the Imaging Service Request Identifier, or Accession Number, as an equivalent of the Admission ID. The SCU of this Query Service does not need to know the true origin or nature of the identifier, only that it is passed in the Query in the Admission ID (0038,0010) Attribute. + + + There is conceptually a datetime of administration Attribute of the Substance Administration act, which is implicitly assumed to be approximately the time of the query in this SOP Class. + + + There is conceptually a dose Attribute of the Product entity, which is the entire product identified by the bar code, and the request is for approval of administration of the entire product. + + + +
+
+ Substance Approval Query Attributes + + defines the Attributes of the Substance Approval Query Information Model. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Substance Approval Query Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ + Patient + +
+ Patient's Name + + (0010,0010) + + O + + 2 + +
+ Patient ID + + (0010,0020) + + R + + 1 + + Shall be retrieved with Single Value Matching only. + One or both of Patient ID (0010,0020) and Admission ID (0038,0010) shall be present as a Matching Key in the Query +
+ Issuer of Patient ID + + (0010,0021) + + O + + 2 + +
+ Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + O + + 3 + +
+ >All Attributes of the Issuer of Patient ID Qualifiers Sequence + + + O + + 3 + +
+ Patient's Birth Date + + (0010,0030) + + - + + 2 + +
+ Patient's Sex + + (0010,0040) + + - + + 2 + +
+ + Visit + +
+ Admission ID + + (0038,0010) + + R + + 2 + + Shall be retrieved with Single Value Matching only. + One or both of Patient ID (0010,0020) and Admission ID (0038,0010) shall be present as a Matching Key in the Query +
+ Issuer of Admission ID Sequence + + (0038,0014) + + O + + 3 + +
+ >Local Namespace Entity ID + + (0040,0031) + + O + + 1C + + Required if Universal Entity ID (0040,0032) is not present; may be present otherwise +
+ >Universal Entity ID + + (0040,0032) + + O + + 1C + + Required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise. +
+ >Universal Entity ID Type + + (0040,0033) + + O + + 1C + + Required if Universal Entity ID (0040,0032) is present. +
+ + Product + +
+ Product Package Identifier + + (0044,0001) + + R + + 1 + + Shall be retrieved with Single Value Matching only. Shall be present as a Matching Key in the Query. +
+ + Substance Administration + +
+ Administration Route Code Sequence + + (0054,0302) + + R + + 1 + + Shall be present as a Matching Key in the Query. +
+ >Code Value + + (0008,0100) + + R + + 1 + +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ + Approval + +
+ Substance Administration Approval + + (0044,0002) + + - + + 1 + +
+ Approval Status Further Description + + (0044,0003) + + - + + 2 + +
+ Approval Status DateTime + + (0044,0004) + + - + + 1 + +
+ One or both of Patient ID (0010,0020) and Admission ID (0038,0010) shall be present as a Matching Key in the Query. + Product Package Identifier (0044,0001) shall be present as a Matching Key in the Query. The Product Package Identifier might not be globally unique and might conflict with other identifiers used within the scope of the institution. + + The package identifiers are typically unique within the scope of the substance administration management systems. This is a warning that they are not UIDs. + + Administration Route Code Sequence (0054,0302) shall be present as a Matching Key in the Query, and a single Item shall be present in that Sequence with Code Value (0008,0100) and Coding Scheme Designator (0008,0102) as Matching Keys. +
+
+ Substance Approval Query Responses + A Query response may have a status of Success or Failure (see ). A Failure Query response carries no semantics about the existence or status of approval of the Substance Administration. + A successful Query response will contain zero or one Pending response items. The case of zero Pending responses carries the semantics of no matching Approval Information Entity found, i.e., that the SCP cannot determine an approval, rather than that the substance administration is approved or disapproved. In this case a decision on substance administration needs to be made by the healthcare provider. + + Zero Pending responses may occur due do inability of the SCP to match the patient ID, product ID or route of administration. + + In the case of one Pending response, the matching Approval Information Entity will explicitly convey the Substance Administration Approval (0044,0002) value of APPROVED, WARNING, or CONTRA_INDICATED. +
+
+
+ Conformance Requirements + An implementation may conform to the Substance Approval Query SOP Class as an SCU or an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that conforms to the Substance Approval Query SOP Class shall support queries against the Information Model described in using the baseline C-FIND SCU Behavior described in . + An implementation that conforms to the Substance Approval Query SOP Class as an SCU shall state in its Conformance Statement how the Query Attributes are used in the application, and how the application displays the returned Attributes, in particular the values of Substance Administration Approval (0044,0002) and Approval Status Further Description (0044,0003). It shall state how it indicates a Query response with zero Pending items, or a Failure status. + An implementation that conforms to the Substance Approval Query SOP Class as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ SCP Conformance + An implementation that conforms to the Substance Approval Query SOP Class shall support queries against the Substance Approval Query Information Model described in using the C-FIND SCP Behavior described in . It shall support all of the Attributes specified in the Information Model. + An implementation that conforms to the Substance Approval Query SOP Class as an SCP shall state in its Conformance Statement how it processes Required and Optional Matching Key Attributes. It shall state how it obtains the values for the Return Key Attributes. + An implementation that conforms to the Substance Approval Query SOP Class as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching and encoding responses. +
+
+
+ SOP Class + The Substance Approval Query SOP Class in the Substance Administration Service Class identifies the Substance Approval Query Information Model, and the DIMSE-C operations supported. The following Standard SOP Class is identified: + + + + + + + + + + + + + + +
Substance Approval Query SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Substance Approval Query Information Model - FIND + + 1.2.840.10008.5.1.4.42 +
+
+
+
+
+ + Color Palette Storage Service Class + See . + + The requirements of this section have been consolidated into the Non-Patient Object Storage Service Class (see ). + + + + Color Palette Query/Retrieve Service Class +
+ Overview +
+ Scope + The Color Palette Query/Retrieve Service Class defines an application-level class-of-service that facilitates access to Color Palette composite objects. +
+
+ Conventions + See Conventions for the Basic Worklist Management Service (K.1.2). +
+
+ Query/Retrieve Information Model + In order to serve as an SCP of the Color Palette Query/Retrieve Service Class, a DICOM AE possesses information about the Attributes of a number of Color Palette composite SOP Instances. The information is organized into a Color Palette Information Model. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Color Palette Query/Retrieve Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Color Palette Query/Retrieve Service Class are implemented using the DIMSE-C C-FIND, C-MOVE and C-GET services as defined in . + The semantics of the C-FIND service are the same as those defined in the Service Definition of the Basic Worklist Management Service Class. + The semantics of the C-MOVE and C-GET services are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. +
+
+
+ Color Palette Information Model Definition + The Color Palette Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + The Color Palette Information Model is defined, with the Entity-Relationship Model Definition and Key Attributes Definition analogous to those defined in the Worklist Information Model Definition of the Basic Worklist Management Service. +
+
+ Color Palette Information Model + The Color Palette Information Model is based upon a one level entity: + + + Color Palette object instance + + + The Color Palette object instance contains Attributes associated with the Color Palette object IE of the Composite IODs as defined in . +
+
+ DIMSE-C Service Groups +
+ C-FIND Operation + See the C-FIND Operation definition for the Basic Worklist Management Service Class (K.4.1), and substitute "Color Palette" for "Worklist. The "Worklist" Search Method shall be used. + The SOP Class UID identifies the Color Palette Information Model against which the C-FIND is to be performed. The Key Attributes and values allowable for the query are defined in the SOP Class definition for the Color Palette Information Model. +
+
+ C-MOVE Operation + See the C-MOVE Operation definition for the Query/Retrieve Service Class (C.4.2). No Extended Behavior or Relational-Retrieve is defined for the Color Palette Query/Retrieve Service Class. + Query/Retrieve Level (0008,0052) is not relevant to the Color Palette Query/Retrieve Service Class, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+ C-GET Operation + See the C-GET Operation definition for the Query/Retrieve Service Class (C.4.3). No Extended Behavior or Relational-Retrieve is defined for the Color Palette Query/Retrieve Service Class. + Query/Retrieve Level (0008,0052) is not relevant to the Color Palette Query/Retrieve Service Class, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+
+ Association Negotiation + See the Association Negotiation definition for the Basic Worklist Management Service Class (K.5). +
+
+ SOP Class Definitions +
+ Color Palette Information Model +
+ E/R Model + The Color Palette Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Color Palette Instance. + +
+ Color Palette Information Model E/R Diagram + + + + + + +
+
+
+
+ Color Palette Attributes + + defines the Attributes of the Color Palette Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Color Palette Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + + This Attribute shall be retrieved with Single Value matching. +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + + This Attribute shall be retrieved with Single Value matching. +
+ + Color Palette Definition + +
+ Content Label + + (0070,0080) + + R + + 1 + + This Attribute shall be retrieved with Single Value, Wild Card or Universal matching. +
+ Content Description + + (0070,0081) + + - + + 2 + +
+ Content Creator's Name + + (0070,0084) + + - + + 2 + +
+ Alternate Content Description Sequence + + (0070,0087) + + - + + 3 + +
+ >Content Description + + (0070,0081) + + - + + 1 + +
+ >Language Code Sequence + + (0008,0006) + + - + + 1 + +
+ >>Code Value + + (0008,0100) + + - + + 1 + +
+ >>Coding Scheme Designator + + (0008,0102) + + - + + 1 + +
+ >>Coding Scheme Version + + (0008,0103) + + - + + 3 + +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+
+
+ Conformance Requirements + An implementation may conform to one of the Color Palette Information Model SOP Classes as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to one of the Color Palette Information Model SOP Classes shall support queries against the Color Palette Information Model using the C-FIND SCU Behavior described for the Basic Worklist Management Service Class (see and ). + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCU shall state in its Conformance Statement whether it requests Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCU shall support transfers against the Color Palette Information Model using the C-MOVE SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+ C-GET SCU Conformance + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCU shall support transfers against the Color Palette Information Model using the C-GET SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP shall support queries against the Color Palette Information Model using the C-FIND SCP Behavior described for the Basic Worklist Management Service Class (see ). + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP shall state in its Conformance Statement whether it supports Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching and encoding responses. +
+
+ C-MOVE SCP Conformance + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP shall support transfers against the Color Palette Information Model using the C-MOVE SCP baseline behavior described for the Query/Retrieve Service Class (see ). + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP, which generates transfers using the C-MOVE operation, shall state in its Conformance Statement the Color Palette Storage Service Class SOP Class under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP shall support transfers against the Color Palette Information Model using the C-GET SCP baseline behavior described for the Query/Retrieve Service Class (see ). + An implementation that conforms to one of the Color Palette Information Model SOP Classes as an SCP, which generates transfers using the C-GET operation, shall state in its Conformance Statement the Color Palette Storage Service Class SOP Class under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+
+ SOP Classes + The SOP Classes of the Color Palette Information Model in the Color Palette Query/Retrieve Service Class identify the Color Palette Information Model, and the DIMSE-C operations supported. The following Standard SOP Classes are identified: + + + + + + + + + + + + + + + + + + + + + + +
Color Palette SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Color Palette Information Model - FIND + + 1.2.840.10008.5.1.4.39.2 +
+ Color Palette Information Model - MOVE + + 1.2.840.10008.5.1.4.39.3 +
+ Color Palette Information Model - GET + + 1.2.840.10008.5.1.4.39.4 +
+
+
+
+
+ + Instance and Frame Level Retrieve SOP Classes (Normative) +
+ Overview +
+ Scope + Composite Instance Root Retrieve Service is a service within the DICOM Query/Retrieve Service class defined in .The retrieve capability of this service allows a DICOM AE to retrieve Composite Instances or selected frames from a remote DICOM AE over a single Association or request the remote DICOM AE to initiate a transfer of Composite Object Instances or selected frames from image objects to another DICOM AE. + The Enhanced Multi-Frame Image Conversion Extended Negotiation Option of the DICOM Query/Retrieve Service class defined in is also supported for the Composite Instance Root Retrieve Service. +
+
+ Composite Instance Root Retrieve Information Model + Retrievals are implemented against the Composite Instance Root Retrieve Information Model, as defined in this Annex of the DICOM Standard. A specific SOP Class of the Query/Retrieve Service Class consists of an Information Model Definition and a DIMSE-C Service Group. +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Composite Instance Root Retrieve Service with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Composite Instance Root Retrieve Service are implemented using the DIMSE-C C-MOVE and C-GET services as defined in . + The following descriptions of the DIMSE-C C-GET and C-MOVE services provide a brief overview of the SCU/SCP semantics: + + + A C-MOVE service conveys the following semantics: + + + The SCU supplies Unique and Frame Range Key values to identify the requested SOP Instance(s). The SCP creates new SOP instances if necessary and then initiates C-STORE sub-operations for the corresponding storage SOP Instances. These C-STORE sub-operations occur on a different Association than the C-MOVE service. The SCP role of the Retrieve SOP Class and the SCU role of the Storage SOP Class may be performed by different applications that may or may not reside on the same system. Initiation mechanism of C-STORE sub-operations is outside of the scope of DICOM standard. + + + The SCP may optionally generate responses to the C-MOVE with status equal to Pending during the processing of the C-STORE sub-operations. These C-MOVE responses indicate the number of Remaining C-STORE sub-operations and the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. + + + When the number of Remaining C-STORE sub-operations reaches zero, the SCP generates a final response with a status equal to Success, Warning, Failed, or Refused. This response shall indicate the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. If any of the sub-operations was successful then a Successful UID list shall be returned. If the status of a C-STORE sub-operation was Failed a UID List shall be returned. + + + The SCU may cancel the C-MOVE service by issuing a C-MOVE-CANCEL request at any time during the processing of the C-MOVE. The SCP terminates all incomplete C-STORE sub-operations and returns a status of Canceled. + + + + + A C-GET service conveys the following semantics: + + + The SCU supplies Unique and Frame Range Key values to identify the requested SOP Instance(s). The SCP creates new SOP instances if necessary and then generates C-STORE sub-operations for the corresponding storage SOP Instances. These C-STORE sub-operations occur on the same Association as the C-GET service and the SCU/SCP roles are reversed for the C-STORE. + + + The SCP may optionally generate responses to the C-GET with status equal to Pending during the processing of the C-STORE sub-operations. These C-GET responses indicate the number of remaining C-STORE sub-operations and the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. + + + When the number of Remaining C-STORE sub-operations reaches zero, the SCP generates a final response with a status equal to Success, Warning, Failed, or Refused. This response shall indicate the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. If the status of any C-STORE sub-operation was Failed a UID List shall be returned. + + + The SCU may cancel the C-GET service by issuing a C-GET-CANCEL request at any time during the processing of the C-GET. The SCP terminates all incomplete C-STORE sub-operations and returns a status of Canceled. + + + + +
+
+
+ Composite Instance Root Retrieve Information Model Definition + The Composite Instance Root Retrieve Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + + This SOP Class identifies the class of the Composite Instance Root Retrieve Information Model (i.e., not the SOP Class of the stored SOP Instances for which the SCP has information). + + Information Model Definitions for standard SOP Classes of the Composite Instance Root Retrieve Service are defined in this Annex. A Composite Instance Root Retrieve Information Model Definition contains: + + + Entity-Relationship Model Definition + + + Key Attributes Definition + + +
+ Entity-Relationship Model Definition + For any Composite Instance Root Retrieve Information Model, an Entity-Relationship Model defines a hierarchy of entities, with Attributes defined for each level in the hierarchy (e.g., Composite Instance, Frame).. +
+
+ Attributes Definition + Attributes and matching shall be as defined in section + +
+
+
+ Standard Composite Instance Root Retrieve Information Model + One standard Composite Instance Root Retrieve Information Model is defined in this Annex. The Composite Instance Root Retrieve Information Model is associated with a number of SOP Classes. The following hierarchical Composite Instance Root Retrieve Information Model is defined: + + + Composite Instance Root + + +
+ Composite Instance Root Information Model + The Composite Instance Root Information Model is based upon a two level hierarchy: + + + Composite Instance + + + Frame + + + The Composite Instance level is the top level and contains only the SOP Instance UID. The Frame level is below the Composite Instance level and contains only the Attributes that refer to a selection of the frames from a single multi-frame image object. +
+
+ Construction and Interpretation of Frame Range Keys + The following rules for the use of Frame Range Keys apply to both an SCU creating such keys and to an SCP interpreting them. +
+ Frame List definitions + The selection of frames to be included in a new created SOP Instance made in response to a FRAME level Composite Instance Root Retrieve request shall be defined by one of the mechanisms defined in this section, and the list of frames so specified shall be referred to as the "Frame List". + + + + Re-ordering of frames is not supported, and order of the frames in the Frame List will always be the same as in the original SOP Instance. + + + New allowable frame selection mechanisms may be defined in the future by the addition of new SOP classes + + + +
+ Simple Frame List + Simple Frame List (0008,1161) is a multi-valued Attribute containing a list of frame numbers, each specifying a frame to be included in the returned object. The first frame of the source instance shall be denoted as frame number 1. + The frame numbers in the list shall not contain any duplicates, and shall increase monotonically. + + Due to the use of UL for this element, a maximum of 16383 values may be specified, as only a 2 byte length is available when an explicit VR Transfer Syntax is used. + +
+
+ Calculated Frame List + Calculated Frame List (0008,1162) is a multi-valued Attribute containing a list of 3-tuples, each representing a sub-range of frames to be included in the returned object. The first frame of the source instance shall be denoted as frame number 1.For each 3-tuple: . + + + The first number shall be the frame number of the first frame of the sub-range. + + + The second number shall be the upper limit of the sub-range, and shall be greater than or equal to the first number. + + + The third number shall be the increment between requested frames of the sub-range. This shall be greater than zero. + + + The difference between the first and second numbers is not required to be an exact multiple of the increment. + If the difference between the first and second numbers is an exact multiple of the increment, then the last frame of the sub-range shall be the second number. + If the first number is greater than the number of frames in the referenced SOP Instance then that sub-range shall be ignored. + The sub-ranges shall be non-overlapping such that the sequence of frame numbers determined by concatenating all the sub-ranges shall not contain any duplicates, and shall increase monotonically. A value of FFFFFFFFH or any value greater than the number of frames in the referenced SOP Instance as the second value shall denote the end of the set of frames in the referenced SOP Instance, and may only occur in the last 3-tuple. + + For example, if the Calculated Frame List contains 6 values, 2, 9, 3, 12, FFFFFFFFH, 5 and is applied to an Instance containing 25 frames. The resulting Frame List will contain the values 2, 5, 8, 12, 17 and 22. + +
+
+ Time Range + Time Range (0008,1163) contains the start and end times to be included in the returned object. Times are in seconds, relative to the value of the Content Time (0008,0033) in the parent object. + The range shall include all frames between the specified times including any frames at the specified times. + The range may be expanded as a consequence of the format in which the information is stored. Where such expansion occurs, any embedded audio data shall be similarly selected. Under all circumstances, the returned Composite SOP Instance shall retain the relationship between image and audio data. + + For MPEG-2, MPEG-4 AVC/H.264 and HEVC/H.265 this would be to the nearest surrounding Key Frames. + + For JPEG 2000 Part 2, this would be to nearest surrounding precinct or tile boundary + Time Range shall only be used to specify extraction from SOP instances where the times of frames can be ascertained using one or more of the following Attributes: + + + Frame Time (0018,1063) + + + Frame Time Vector (0018,1065) + + + Frame Reference DateTime (0018,9151) in the Frame Content Sequence (0020,9111) + + +
+
+
+
+ New Instance Creation At the Frame Level + When a C-MOVE or C-GET operation is performed on a source Composite Instance at the FRAME level then the SCP shall create a new Composite Instance according to the following rules: + + + The new Composite Instance shall be extracted from the source Composite Instance specified by the SOP Instance UID Unique Key present at the Composite Instance Level. + + + The new Composite Instance shall be an instance of the same SOP Class as the source Composite Instance. + + + The new Composite Instance shall have a new SOP Instance UID. + + + The new Composite Instance shall be a valid SOP Instance. + + + + The new Composite Instance is required to be internally consistent and valid. This may require the SCP to make consistent modification of any Attributes that reference frames or the relationship between them such as start time, time offsets, and modifying the Per-frame Functional Group Sequence (5200,9230). + + + + The new Composite Instance shall contain the frames from the source Composite Object as requested in the Requested Frame List. The Requested Frame List shall be interpreted according to the rules in . The frames shall be in the same order as in the source Composite Instance. + + + The new Composite Instance shall include the Frame Extraction Module, which shall contain appropriate Attributes from the identifier of the C-GET or C-MOVE request that caused this instance to be created. If the Frame Extraction Module already exists in the source Composite Instance, then a new item shall be appended as an additional item into the Frame Extraction Sequence. + + + The new Composite Instance shall contain the Contributing Equipment Sequence (0018,A001). If the source Composite Object contains the Contributing Equipment Sequence, then a new Item shall be appended to the copy of the sequence in the new Composite Instance, and if the source Composite Object does not contain the Contributing Equipment Sequence, then it shall be created, containing one new Item. In either case, the new Item shall describe the equipment that is extracting the frames, and the Purpose of Reference Code Sequence (0040,A170) within the Item shall be (109105, DCM, "Frame Extracting Equipment"). + + + + The existing General Equipment Module cannot be used to hold details of the creating equipment, as it is a Series level Module. The new Composite Instance is part of the same Series as the source Instance, and therefore the Series level information cannot be altered. + + + + The new Composite Instance shall have the same Patient, Study and Series level information as the source Instance, including Study and Series Instance UIDs. + + + The new Composite Instance shall have the same values for the Attributes of the Image Pixel Module of the source Composite Instance except that the Pixel Data Provider URL (0028,7FE0) Attribute shall not be present,Pixel Data (7FE0,0010) shall be replaced by the subset of frames, as specified in section , and Number of Frames (0028,0008) shall contain the number of frames in the new Composite Instance. + + + The new Composite Instance shall have the same values for other Type 1 and Type 2 Image level Attributes that are not otherwise specified. Other Attributes may be included in the new Composite Instance if consistent with the new Composite Instance. + + + + In most cases private Attributes should not be copied unless their full significance is known. See for more guidance. + + + + The new Composite Instance shall not be contained in a Concatenation. This means that it shall not contain a Concatenation UID (0020,9161) Attribute or other Concatenation Attributes. If the existing Composite Instance contains such Attributes, they shall not be included in the new Composite Instance. + + +
+
+
+ DIMSE-C Service Groups + A single DIMSE-C Service is used in the construction of SOP Classes of the Composite Instance Root Retrieve Service. The following DIMSE-C operation is used: + + + C-MOVE + + + C-GET + + +
+ C-MOVE Operation + SCUs of the Composite Instance Root Retrieve Service shall generate retrievals using the C-MOVE operation as described in .The C-MOVE operation allows an application entity to instruct another application entity to transfer stored SOP Instances or new SOP Instances extracted from such stored SOP Instances to another application entity using the C-STORE operation. Support for the C-MOVE service shall be agreed upon at Association establishment time by both the SCU and SCP of the C-MOVE in order for a C-MOVE operation to occur over the Association. The C-STORE sub-operations shall always be accomplished over an Association different from the Association that accomplishes the CMOVE operation. Hence, the SCP of the Query/Retrieve Service Class serves as the SCU of the Storage Service Class. + + The application entity that receives the stored SOP Instances may or may not be the originator of the C-MOVE operation. + + A C-MOVE request may be performed to any level of the Composite Object Instance Root Retrieve Information Model,and the expected SCP behavior depends on the level selected. +
+ C-MOVE Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Query/Retrieve Information Model against which the C-MOVE is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-MOVE operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-MOVE operation and corresponding C-STORE sub-operations with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing, and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. The same priority shall be used for all C-STORE sub-operations. +
+
+ Identifier + The C-MOVE request shall contain an Identifier. The C-MOVE response shall conditionally contain an Identifier as required in . + + The Identifier is specified as U in the definition of the C-MOVE primitive in but is specialized for use with this service. + +
+ Request Identifier Structure + An Identifier in a C-MOVE request shall contain: + + + the Query/Retrieve Level (0008,0052) that defines the level of the retrieval + + + SOP Instance UID(s) (0008,0018) + + + One of the Frame Range Keys if present in the Information Model for the level of the Retrieval + + + Conditionally, the Attribute Query/Retrieve View (0008,0053). This Attribute may be included if Enhanced Multi-Frame Image Conversion has accepted during Association Extended Negotiation. It shall not be included otherwise. + + + Specific Character Set (0008,0005) shall not be present. + The Keys at each level of the hierarchy and the values allowable for the level of the retrieval shall be defined in the SOP Class definition for the Query/Retrieve Information Model. +
+
+
+ Status + The status code values that might be returned in a C-MOVE response shall be as specified in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-MOVE Response Status Values for Instance Root Retrieve
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources - Unable to calculate number of matches + + A701 + + (0000,0902) +
+ Refused: Out of Resources - Unable to perform sub-operations + + A702 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Refused: Move Destination unknown + + A801 + + (0000,0902) +
+ Identifier does not match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ Unable to process + + Cxxx + + (0000,0901) + (0000,0902) +
+ None of the frames requested were found in the SOP Instance + + AA00 + + (0000,0902) +
+ Unable to create new object for this SOP class + + AA01 + + (0000,0902) +
+ Unable to extract frames + + AA02 + + (0000,0902) +
+ Time-based request received for a non-time-based original SOP Instance. + + AA03 + + (0000,0902) +
+ Invalid Request + + AA04 + + (0000,0901) + (0000,0902) +
+ Cancel + + Sub-operations terminated due to Cancel Indication + + FE00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Warning + + Sub-operations Complete - One or more Failures or Warnings + + B000 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Success + + Sub-operations Complete - No Failures or Warnings + + 0000 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Pending + + Sub-operations are continuing + + FF00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+
+
+ Number of Remaining Sub-Operations + Inclusion of the Number of Remaining Sub-operations shall be as specified in + +
+
+ Number of Completed Sub-Operations + Inclusion of the Number of Completed Sub-operations shall be as specified in + +
+
+ Number of Failed Sub-Operations + Inclusion of the Number of Failed Sub-operations shall be as specified in + +
+
+ Number of Warning Sub-Operations + Inclusion of the Number of Warning Sub-operations shall be as specified in . +
+
+
+ C-MOVE SCU Behavior +
+ Baseline Behavior of SCU + An SCU conveys the following semantics with a C-MOVE request: + + + If the Retrieve Level (0000,0052) is IMAGE, the SCU shall specify one SOP Instance UID or a list of SOP Instance UIDs. + + + If the Retrieve Level (0000,0052) is FRAME, the SCU shall specify the single SOP Instance UID of the item from which the new Composite SOP Instance should be extracted and the requested Frame List. The Requested Frame List shall be constructed as defined in . + + + The SCU shall accept C-MOVE responses with status equal to Pending during the processing of the C-STORE sub-operations. These responses indicate the number of Remaining, Completed, Failed and Warning C-STORE sub-operations. + + + The SCU shall interpret a C-MOVE response with a status equal to Success, Warning, Failure, or Refused as a final response. The final response indicates the number of Completed sub-operations and the number of Failed C-STORE sub-operations resulting from the C-MOVE operation. The SCU shall interpret a status of: + + + Success to indicate that all sub-operations were successfully completed + + + Failure or Refused to indicate all sub-operations were unsuccessful + + + Warning in all other cases. The Number of Completed Sub-Operations (0000,1021), Number of Warning Sub-Operations (0000,1023), Number of Failed Sub-Operations (0000,1022) can be used to obtain more detailed information. + + + + + The SCU may cancel the C-MOVE operation by issuing a C-MOVE-CANCEL request at any time during the processing of the C-MOVE request. A C-MOVE response with a status of Canceled shall indicate to the SCU that the retrieve was canceled. Optionally, the C-MOVE response with a status of Canceled shall indicate the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-MOVE-CANCEL request. + + + + For FRAME level C-MOVE operations, the application receiving the C-STORE sub-operations will receive a new SOP Instance with a different SOP Instance UID from the one included in the C-MOVE request. If it is required to link the received instance to the request, then it may be necessary to inspect the Frame Extraction Sequence of the instance received, to compare the original Instance UID and Requested Frame List to those in the request. + +
+
+ Extended Behavior of SCU + The extended behavior of the SCU shall be as specified in , except that Relational-retrieve shall not be supported. +
+
+
+ C-MOVE SCP Behavior +
+ Baseline Behavior of SCP + An SCP conveys the following semantics with a C-MOVE response: + + + If the Retrieve Level (0000,0052) is IMAGE the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-MOVE request. + + + If the Retrieve Level (0000,0052) is FRAME, the SCP shall create a new Composite Instance according to the rules in section . The newly created SOP Instance shall be treated in the same manner as the set of Entities identified above. + + + The SCP shall either re-use an established and compatible Association or establish a new Association for the C-STORE sub-operations + + + The SCP shall initiate C-STORE sub-operations over the Association for the identified or newly created SOP Instances. + + + A sub-operation is considered a Failure if the SCP is required to create new SOP Instance, but is unable to do so due to inconsistencies in the Frame Range Keys, or if the resulting SOP Instance would not be valid. + + + Optionally, the SCP may generate responses to the C-MOVE with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the number of Remaining, Completed, Failure, and Warning C-STORE sub-operations. + + + When the number of Remaining sub-operations reaches zero, the SCP shall generate a final response with a status equal to Success, Warning or Failed. The status contained in the C-MOVE response shall contain: + + + Success if all sub-operations were successfully completed + + + Failure if all sub-operations were unsuccessful + + + Warning in all other cases. + + + + + The SCP may receive a C-MOVE-CANCEL request at any time during the processing of the C-MOVE request. The SCP shall interrupt all C-STORE sub-operation processing and return a status of Canceled in the C-MOVE response. The C-MOVE response with a status of Canceled shall contain the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-MOVE-CANCEL request. + + + If the SCP manages images in multiple alternate encodings (see ), only one of the alternate encodings of an image shall be used as the existing SOP Instance from which frames are to be extracted. + + +
+
+ Extended Behavior of SCP + The extended behavior of the SCP shall be as specified in , except that Relational-retrieve shall not be supported. +
+
+
+
+ C-GET Operation + SCUs of the Composite Instance Root Retrieve Service shall generate retrievals using the C-GET operation as described in . The C-GET operation allows an application entity to instruct another application entity to transfer stored SOP Instances or new SOP Instances derived from such stored SOP Instances to the initiating application entity using the C-STORE operation. Support for the C-GET service shall be agreed upon at Association establishment time by both the SCU and SCP of the C-GET in order for a C-GET operation to occur over the Association. The C-STORE Sub-operations shall be accomplished on the same Association as the C-GET operation. Hence, the SCP of the Query/Retrieve Service Class serves as the SCU of the Storage Service Class. + + The Application Entity that receives the stored SOP Instances is always the originator of the C-GET operation. + + A C-GET request may be performed to any level of the Composite Instance Root Retrieve Information Model, and the expected SCP behavior depends on the level selected. +
+ C-GET Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Query/Retrieve Information Model against which the C-GET is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-GET operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-GET operation and corresponding C-STORE sub-operations with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing, and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. The same priority shall be used for all C-STORE sub-operations. +
+
+ Identifier + The C-GET request shall contain an Identifier. The C-GET response shall conditionally contain an Identifier as required in . + + The Identifier is specified as U in the definition of the C-GET primitive in but is specialized for use with this service. + +
+ Request Identifier Structure + An Identifier in a C-GET request shall contain: + + + the Query/Retrieve Level (0008,0052) that defines the level of the retrieval + + + SOP Instance UID(s) (0008,0018) + + + One of the Frame Range Keys if present in the Information Model for the level of the Retrieval + + + Conditionally, the Attribute Query/Retrieve View (0008,0053). This Attribute may be included if Enhanced Multi-Frame Image Conversion has accepted during Association Extended Negotiation. It shall not be included otherwise. + + + Specific Character Set (0008,0005) shall not be present. + The Keys at each level of the hierarchy and the values allowable for the level of the retrieval shall be defined in the SOP Class definition for the Query/Retrieve Information Model. +
+
+
+ Status + The status code values that might be returned in a C-GET response shall be as specified in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-GET Response Status Values for Instance Root Retrieve
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources - Unable to calculate number of matches + + A701 + + (0000,0902) +
+ Refused: Out of Resources - Unable to perform sub-operations + + A702 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Identifier does not match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ Unable to process + + Cxxx + + (0000,0901) + (0000,0902) +
+ None of the frames requested were found in the SOP Instance + + AA00 + + (0000,0902) +
+ Unable to create new object for this SOP class + + AA01 + + (0000,0902) +
+ Unable to extract frames + + AA02 + + (0000,0902) +
+ Time-based request received for a non-time-based original SOP Instance. + + AA03 + + (0000,0902) +
+ Invalid Request + + AA04 + + (0000,0901) + (0000,0902) +
+ Cancel + + Sub-operations terminated due to Cancel Indication + + FE00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Warning + + Sub-operations Complete - One or more Failures or Warnings + + B000 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Success + + Sub-operations Complete - No Failures or Warnings + + 0000 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Pending + + Sub-operations are continuing + + FF00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+
+
+ Number of Remaining Sub-Operations + Inclusion of the Number of Remaining Sub-operations shall be as specified in + +
+
+ Number of Completed Sub-Operations + Inclusion of the Number of Completed Sub-operations shall be as specified in + +
+
+ Number of Failed Sub-Operations + Inclusion of the Number of Failed Sub-operations shall be as specified in + +
+
+ Number of Warning Sub-Operations + Inclusion of the Number of Warning Sub-operations shall be as specified in . +
+
+
+ C-GET SCU Behavior +
+ Baseline Behavior of SCU + An SCU conveys the following semantics with a C-GET request: + + + If the Retrieve Level (0000,0052) is IMAGE, the SCU shall specify one SOP Instance UID or a list of SOP Instance UIDs. + + + If the Retrieve Level (0000,0052) is FRAME, the SCU shall specify the single SOP Instance UID of the item from which the new Composite SOP Instance should be extracted and the Requested Frame List. The Requested Frame List shall be constructed as a Frame List as defined in . + + + The SCU shall have proposed sufficient presentation contexts at Association establishment time to accommodate expected C-STORE sub-operations that will occur over the same Association. The SCU of the Query/Retrieve Service Class shall serve as the SCP of the Storage Service Class. + + + The SCU shall accept C-GET responses with status equal to Pending during the processing of the C-STORE sub-operations. These responses indicate the number of Remaining, Completed, Failed and Warning C-STORE sub-operations. + + + The SCU shall interpret a C-GET response with a status equal to Success, Warning, Failure, or Refused as a final response. The final response indicates the number of Completed sub-operations and the number of Failed C-STORE sub-operations resulting from the C-GET operation. The SCU shall interpret a status of: + + + Success to indicate that all sub-operations were successfully completed + + + Failure or Refused to indicate all sub-operations were unsuccessful + + + Warning in all other cases. The Number of Completed Sub-Operations (0000,1021), Number of Warning Sub-Operations (0000,1023), Number of Failed Sub-Operations (0000,1022) can be used to obtain more detailed information. + + + + + The SCU may cancel the C-GET operation by issuing a C-GET-CANCEL request at any time during the processing of the C-GET request. A C-GET response with a status of Canceled shall indicate to the SCU that the retrieve was canceled. Optionally, the C-GET response with a status of Canceled shall indicate the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-GET-CANCEL request. + + +
+
+ Extended Behavior of SCU + The extended behavior of the SCU shall be as specified in , except that Relational-retrieve shall not be supported. +
+
+
+ C-GET SCP Behavior +
+ Baseline Behavior of SCP + An SCP conveys the following semantics with a C-GET response: + + + If the Retrieve Level (0000,0052) is IMAGE the SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-GET request. + + + If the Retrieve Level (0000,0052) is FRAME, the SCP shall create a new Composite Instance according to the rules in section . The newly created SOP Instance shall be treated in the same manner as the set of Entities identified above. + + + The SCP shall initiate C-STORE sub-operations for the identified or newly created SOP Instances. The SCP of the Query/Retrieve Service Class shall serve as an SCU of the Storage Service Class. + + + The SCP shall initiate C-STORE sub-operations over the same Association for all identified or newly created SOP Instances specified in the C-GET request. + + + A sub-operation is considered a Failure if the SCP is required to create new SOP Instance, but is unable to do so due to inconsistencies in the Frame Range Keys, or if the resulting SOP Instance would not be valid. + + + A sub-operation is considered a Failure if the SCP is unable to initiate a C-STORE sub-operation because the Query/Retrieve SCU did not offer an appropriate presentation context for a given stored SOP Instance. + + + Optionally, the SCP may generate responses to the C-GET with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the number of Remaining, Completed, Failure, and Warning C-STORE sub-operations. + + + When the number of Remaining sub-operations reaches zero, the SCP shall generate a final response with a status equal to Success, Warning or Failed. The status contained in the C-GET response shall contain: + + + Success if all sub-operations were successfully completed + + + Failure if all sub-operations were unsuccessful + + + Warning in all other cases. + + + + + The SCP may receive a C-GET-CANCEL request at any time during the processing of the C-GET request. The SCP shall interrupt all C-STORE sub-operation processing and return a status of Canceled in the C-GET response. The C-GET response with a status of Canceled shall contain the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-GET-CANCEL request. + + + If the SCP manages images in multiple alternate encodings (see ), only one of the alternate encodings of an image shall be used as the existing SOP Instance from which frames are to be extracted. + + +
+
+ Extended Behavior of SCP + The extended behavior of the SCP shall be as specified in , except that Relational-retrieve shall not be supported. +
+
+
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. AEs supporting DICOM Query/Retrieve SOP Classes utilize Association establishment negotiation by defining the use of Application Association Information. See for an overview of Association negotiation. + SOP Classes of the Composite Instance Root Retrieve Service, which include retrieval services based on the C-MOVE and C-GET operations, use the SCP/SCU Role Selection Sub-Item to identify the SOP Classes that may be used for retrieval. +
+ Association Negotiation for C-MOVE and C-GET SOP Classes + Rules are as specified in , except that the extended negotiation sub-item, if used, shall be used as defined in . + + + + Though converted images may be specified by their SOP Instance UID in the Request Identifier, which is always at or below the instance level, there remains a need for extended negotiation and specification of the Query/Retrieve View in order to assure that referential integrity is maintained within the returned SOP Instances (e.g., that a reference to a SOP Instance UID is to a converted image or not, as appropriate). + + + Relational-retrieval is not applicable to these SOP Classes, hence the Extended Negotiation Sub-Item does not include the use of that byte. + + + +
+ SOP Class Extended Negotiation + The SOP Class Extended Negotiation allows, at Association establishment, peer DICOM AEs to exchange application Association information defined by specific SOP Classes. This is achieved by defining the Service-class-application-information field. The Service-class-application-information field is used to define support for Enhanced Multi-Frame Image Conversion. + This negotiation is optional. If absent, the default condition shall be: + + + no Enhanced Multi-Frame Image Conversion support + + + The Association-requester, for each SOP Class, may use one SOP Class Extended Negotiation Sub-Item. The SOP Class is identified by the corresponding Abstract Syntax Name (as defined by ) followed by the Service-class-application-information field. This field defines: + + + Enhanced Multi-Frame Image Conversion support by the Association-requester + + + The Association-acceptor, for each SOP Class Extended Negotiation Sub-Item offered, either accepts the Association-requester proposal by returning the same value (1) or turns down the proposal by returning the value (0). + If the SOP Class Extended Negotiation Sub-Item is not returned by the Association-acceptor then Enhanced Multi-Frame Image Conversion is not supported (default condition). + If the SOP Class Extended Negotiation Sub-Items do not exist in the A-ASSOCIATE indication they shall be omitted in the A-ASSOCIATE response. +
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-RQ) + The SOP Class Extended Negotiation Sub-Item consists of a sequence of mandatory fields as defined by . defines the Service-class-application-information field for the C-MOVE and C-GET operations. + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-RQ
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Unused + + Reserved - shall be 0 +
+ 2 + + Enhanced Multi-Frame Image Conversion + + This byte field defines whether or not the Attribute Query/Retrieve View (0008,0053) shall be used to adjust the view returned in queries to consider conversion to or from Enhanced Multi-Frame Images. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Query/Retrieve View not supported + 1 - Query/Retrieve View supported +
+
+
+ SOP Class Extended Negotiation Sub-Item Structure (A-ASSOCIATE-AC) + The SOP Class Extended Negotiation Sub-Item consists of a sequence of mandatory fields as defined by . defines the Service-class-application-information field for the C-MOVE and C-GET operations. + + + + + + + + + + + + + + + + + + + + + +
SOP Class Extended Negotiation Sub-Item (Service-Class-Application-Information Field) - A-ASSOCIATE-AC
+ + Item Bytes + + + + Field Name + + + + Description of Field + +
+ 1 + + Unused + + Reserved - shall not be tested. +
+ 2 + + Enhanced Multi-Frame Image Conversion + + This byte field defines whether or not the Attribute Query/Retrieve View (0008,0053) shall be used to adjust the view returned in queries to consider conversion to or from Enhanced Multi-Frame Images. It shall be encoded as an unsigned binary integer and shall use one of the following values + 0 - Query/Retrieve View not supported + 1 - Query/Retrieve View supported +
+
+
+
+
+
+ SOP Class Definitions +
+ Composite Instance Root SOP Class Group + In the Composite Instance Root Retrieve Only Information Model, the information is arranged into two levels that correspond to one of the two values in element (0008,0052) shown in . + + + + + + + + + + + + + + + + + + +
Retrieve Level Values for Composite Instance Root
+ + Retrieve Level + + + + Value in (0008,0052) + +
+ Composite Instance + + IMAGE +
+ Frame + + FRAME +
+ + The use of the word "IMAGE" rather than "Composite Instance" is historical to allow backward compatibility with previous versions of the standard. It should not be taken to mean that Composite Instances of other than image type are not included at the level indicated by the value IMAGE. + +
+ Composite Instance Root Retrieve Only Information Model +
+ E/R Model + The Composite Instance Root Retrieve Only Information Model may be represented by the entity relationship diagram shown in + + +
+ Composite Instance Root Information Model E/R Diagram + + + + + + +
+
+
+
+ Composite Instance Level + + defines the keys at the Composite Instance level of the Composite Instance Root Query/Retrieve Information model. + + + + + + + + + + + + + + + + +
Composite Instance Level Keys for the Composite Instance Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Matching Key Type + +
+ SOP Instance UID + + (0008,0018) + + U +
+
+
+ Frame Level + + defines the keys at the Frame level of the Composite Instance Root Query/Retrieve Information Model. One and only one of the frame level keys listed in shall be present in a FRAME level request + + + + + + + + + + + + + + + + + + + + + + + + + + +
Frame Level Keys for the Composite Instance Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Condition + +
+ Simple Frame List + + (0008,1161) + + Required if Calculated Frame List and Time Range are not present +
+ Calculated Frame List + + (0008,1162) + + Required if Simple Frame List and Approximate Frame Range are not present +
+ Time Range + + (0008,1163) + + Required if Simple Frame List and Calculated Frame List are not present +
+
+
+ Scope of the C-MOVE or C-GET Commands and Sub-Operations + A C-MOVE or C-GET request may be performed to any level of the Query/Retrieve Model. A C-MOVE or C-GET where the Query/Retrieve level is the: + IMAGE level indicates that selected individual Composite Instances shall be transferred + FRAME level indicates that a single new Composite Instance shall be created and transferred + + More than one entity may be retrieved if the Query/Retrieve Level is IMAGE using List of UID matching, but if the Query/Retrieve Level is FRAME then only a single entity may be retrieved. + +
+
+
+ Conformance Requirements + An implementation may conform to one of the Composite Instance Root Retrieve SOP Classes as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-MOVE SCU Conformance + An implementation that conforms to one of the Composite Instance Root Retrieve SOP Classes as an SCU shall support transfers against the Retrieve Information Model described in using the C-MOVE SCU Behavior described in . An implementation that conforms to one of the SOP Classes of the Composite Instance Root SOP Class Group as an SCU, and that generates retrievals using the C-MOVE operation, shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C- MOVE. +
+
+ C-GET SCU Conformance + An implementation that conforms to one of the Composite Instance Root Retrieve SOP Classes as an SCU shall support retrievals against the Retrieve Information Model described in using the C-GET SCU Behavior described in . An implementation that conforms to one of the SOP Classes of the Composite Instance Root SOP Class Group as an SCU, which generates retrievals using the C-GET operation shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+ SCP Conformance + An implementation that conforms to one of the Composite Instance Root Retrieve SOP Classes as an SCP for C-GET operations shall:1) support both levels of the Composite Instance Root Retrieve Only Information Model + 2) support all three Frame Level keys + 3) describe in its conformance statement the transformations it applies to a multi-frame Composite Instance when creating a new Composite Instance as defined in . +
+ C-MOVE SCP Conformance + An implementation that conforms to one of the Composite Instance Root Retrieve SOP Classes as an SCP shall support retrievals against both levels of the Retrieve Information Model described in using the C-MOVE SCP Behavior described in . An implementation that conforms to one of the SOP Classes of the Composite Instance Root SOP Class Group as an SCP, which satisfies retrievals using the C- MOVE operation shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C- MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to one of the Composite Instance Root Retrieve SOP Classes as an SCP shall support retrievals against both levels of the Retrieve Information Model described in using the C-GET SCP Behavior described in . An implementation that conforms to one of the SOP Classes of the Composite Instance Root SOP Class Group as an SCP, and that satisfies retrievals using the C-GET operation, shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+
+ SOP Classes + The SOP Classes in the Composite Instance Root SOP Class Group of the Query/Retrieve Service Class identify the Composite Instance Root Retrieve Only Information Model, and the DIMSE-C operations supported. The Standard SOP Classes are listed in . + + + + + + + + + + + + + + + + + + +
SOP Classes for Composite Instance Query/Retrieve Root
+ + SOP Class Name + + + + SOP Class UID + +
+ Composite Instance Root Retrieve - MOVE + + 1.2.840.10008.5.1.4.1.2.4.2 +
+ Composite Instance Root Retrieve - GET + + 1.2.840.10008.5.1.4.1.2.4.3 +
+
+
+
+
+ + Composite Instance Retrieve Without Bulk Data SOP Classes (Normative) +
+ Overview +
+ Scope + Composite Instance Retrieve Without Bulk Data Service is a service within the DICOM Query/Retrieve Service class defined in .The retrieve capability of this service allows a DICOM AE to retrieve Composite Instances without retrieving their pixel data or other potentially large Attributes as defined in . + The Enhanced Multi-Frame Image Conversion Extended Negotiation Option of the DICOM Query/Retrieve Service class defined in is also supported for the Composite Instance Root Retrieve Service. +
+
+ Composite Instance Retrieve Without Bulk Data Information Model + Retrievals are implemented against the Composite Instance Retrieve Without Bulk Data Information Model, as defined in this Annex of the DICOM Standard. A specific SOP Class of the Query/Retrieve Service Class consists of an Information Model Definition and a DIMSE-C Service Group. +
+
+ Attributes Not Included + The Attributes that shall not be included in the top level of the Data set sent by an SCP of this Service are as defined in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes Not to Be Included in Instances Sent
+ + Attribute Name + + + + Tag + +
+ Pixel Data + + (7FE0,0010) +
+ Float Pixel Data + + (7FE0,0008) +
+ Double Float Pixel Data + + (7FE0,0009) +
+ Pixel Data Provider URL + + (0028,7FE0) +
+ Spectroscopy Data + + (5600,0020) +
+ Overlay Data + + (60xx,3000) +
+ Curve Data + + (50xx,3000) +
+ Audio Sample Data + + (50xx,200C) +
+ Encapsulated Document + + (0042,0011) +
+ + This implies that the pixel data within Icon Image Sequence (0088,0200) Items will be preserved. + + The Waveform Data (5400,1010) Attribute shall not be included within the Waveform Sequence (5400,0100). +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of the Composite Instance Retrieve Without Bulk Data Service with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Composite Instance Retrieve Without Bulk Data Service are implemented using the DIMSE-C C-GET service as defined in . + The following descriptions of the DIMSE-C C-GET service provide a brief overview of the SCU/SCP semantics: + + + A C-GET service conveys the following semantics: + + + The SCP shall identify a set of Entities at the level of the retrieval based upon the values in the Unique Keys in the Identifier of the C-GET request. The SCP shall then generate C-STORE sub-operations for the corresponding storage SOP Instances, but shall not include Attributes as described in in the data sent during those sub-operations. These C-STORE sub-operations occur on the same Association as the C-GET service and the SCU/SCP roles are reversed for the C-STORE. + + If the source instance does not contain any of the Attributes described in then, the version sent via the C-STORE sub-operation would be identical to the original data. This is not an error. + + + + The SCP may optionally generate responses to the C-GET with status equal to Pending during the processing of the C-STORE sub-operations. These C-GET responses indicate the number of remaining C-STORE sub-operations and the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. + + + When the number of Remaining C-STORE sub-operations reaches zero, the SCP generates a final response with a status equal to Success, Warning, Failed, or Refused. This response shall indicate the number of C-STORE sub-operations returning the status of Success, Warning, and Failed. If the status of any C-STORE sub-operation was Failed a UID List shall be returned. + + + The SCU may cancel the C-GET service by issuing a C-GET-CANCEL request at any time during the processing of the C-GET. The SCP terminates all incomplete C-STORE sub-operations and returns a status of Canceled. + + + + +
+
+
+ Composite Instance Retrieve Without Bulk Data Information Model Definition + The Composite Instance Retrieve Without Bulk Data Information Model is identified by the SOP Class negotiated at Association establishment time. The SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + + This SOP Class identifies the class of the Composite Instance Retrieve Without Bulk Data Information Model (i.e., not the SOP Class of the stored SOP Instances for which the SCP has information). + + Information Model Definitions for standard SOP Classes of the Composite Instance Retrieve Without Bulk Data Service are defined in this Annex. A Composite Instance Retrieve Without Bulk Data Information Model Definition contains: + + + Entity-Relationship Model Definition + + + Key Attributes Definition + + +
+ Entity-Relationship Model Definition + For any Composite Instance Retrieve Without Bulk Data Information Model, an Entity-Relationship Model defines a hierarchy of entities, with Attributes defined for each level in the hierarchy (e.g., Composite Instance, Frame).. +
+
+ Attributes Definition + Attributes and matching shall be as defined in section + +
+
+
+ Standard Composite Instance Retrieve Without Bulk Data Information Model + One standard Composite Instance Retrieve Without Bulk Data Information Model is defined in this Annex. The Composite Instance Retrieve Without Bulk Data Information Model is associated with a single SOP Class. The following Composite Instance Retrieve Without Bulk Data Information Model is defined: + + + Retrieve Without Bulk Data + + +
+ Composite Instance Retrieve Without Bulk Data Information Model + The Composite Instance Retrieve Without Bulk Data Information Model is based upon a one level hierarchy: + + + Composite Instance + + + The Retrieve Without Bulk Data Information Model may be represented by the entity relationship diagram shown in . + +
+ Retrieve Without Bulk Data Information Model E/R Diagram + + + + + + +
+
+ The Composite Instance level is the only level and contains only the SOP Instance UID. +
+
+
+ DIMSE-C Service Groups + A single DIMSE-C Service is used in the construction of SOP Classes of the Composite Instance Retrieve Without Bulk Data Service. The following DIMSE-C operation is used: + + + C-GET + + +
+ C-GET Operation + SCUs of the Composite Instance Retrieve Without Bulk Data Service shall generate retrievals using the C-GET operation as described in . The C-GET operation allows an application entity to instruct another application entity to transfer SOP Instances without the Attributes as described in to the initiating application entity using the C-STORE operation. Support for the C-GET service shall be agreed upon at Association establishment time by both the SCU and SCP of the C-GET in order for a C-GET operation to occur over the Association. The C-STORE Sub-operations shall be accomplished on the same Association as the C-GET operation. Hence, the SCP of the Query/Retrieve Service Class serves as the SCU of the Storage Service Class. + + The Application Entity that receives the stored SOP Instances is always the originator of the C-GET operation. + +
+ C-GET Service Parameters +
+ SOP Class UID + The SOP Class UID identifies the Query/Retrieve Information Model against which the C-GET is to be performed. Support for the SOP Class UID is implied by the Abstract Syntax UID of the Presentation Context used by this C-GET operation. +
+
+ Priority + The Priority Attribute defines the requested priority of the C-GET operation and corresponding C-STORE sub-operations with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing, and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. The same priority shall be used for all C-STORE sub-operations. +
+
+ Identifier + The C-GET request shall contain an Identifier. The C-GET response shall conditionally contain an Identifier as required in . + + The Identifier is specified as U in the definition of the C-GET primitive in but is specialized for use with this service. + +
+ Request Identifier Structure + An Identifier in a C-GET request shall contain: + + + the Query/Retrieve Level (0008,0052) that defines the level of the retrieval + + + SOP Instance UID(s) (0008,0018) + + + Conditionally, the Attribute Query/Retrieve View (0008,0053). This Attribute may be included if Enhanced Multi-Frame Image Conversion has accepted during Association Extended Negotiation. It shall not be included otherwise. + + + Query/Retrieve Level (0008,0052) shall be IMAGE. + Specific Character Set (0008,0005) shall not be present. + The Keys at each level of the hierarchy and the values allowable for the level of the retrieval are defined in the SOP Class definition for the Query/Retrieve Information Model. +
+
+
+ Status + The status code values that might be returned in a C-GET response shall be as specified in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-GET Response Status Values for Instance Root Retrieve
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources - Unable to calculate number of matches + + A701 + + (0000,0902) +
+ Refused: Out of Resources - Unable to perform sub-operations + + A702 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Identifier does not match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ Unable to process + + Cxxx + + (0000,0901) + (0000,0902) +
+ Cancel + + Sub-operations terminated due to Cancel Indication + + FE00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Warning + + Sub-operations Complete - One or more Failures or Warnings + + B000 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Success + + Sub-operations Complete - No Failures or Warnings + + 0000 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+ Pending + + Sub-operations are continuing + + FF00 + + (0000,1020) + (0000,1021) + (0000,1022) + (0000,1023) +
+
+
+ Number of Remaining Sub-Operations + Inclusion of the Number of Remaining Sub-operations shall be as specified in + +
+
+ Number of Completed Sub-Operations + Inclusion of the Number of Completed Sub-operations shall be as specified in + +
+
+ Number of Failed Sub-Operations + Inclusion of the Number of Failed Sub-operations shall be as specified in + +
+
+ Number of Warning Sub-Operations + Inclusion of the Number of Warning Sub-operations shall be as specified in . +
+
+
+ C-GET SCU and C-STORE SCP Behavior +
+ Baseline Behavior of SCU + An SCU conveys the following semantics with a C-GET request: + + + The SCU shall specify one Instance UID or a list of Instance UIDs. + + + The SCU shall have proposed sufficient presentation contexts at Association establishment time to accommodate expected C-STORE sub-operations that will occur over the same Association. The SCU of the Query/Retrieve Service Class shall serve as the SCP of the Storage Service Class. + + + The SCP of the Storage Service Class shall not store the incomplete SOP Instance; rather the behavior is implementation defined. + + + The SCU shall accept C-GET responses with status equal to Pending during the processing of the C-STORE sub-operations. These responses indicate the number of Remaining, Completed, Failed and Warning C-STORE sub-operations. + + + The SCU shall interpret a C-GET response with a status equal to Success, Warning, Failure, or Refused as a final response. The final response indicates the number of Completed sub-operations and the number of Failed C-STORE sub-operations resulting from the C-GET operation. The SCU shall interpret a status of: + + + Success to indicate that all sub-operations were successfully completed + + + Failure or Refused to indicate all sub-operations were unsuccessful + + + Warning in all other cases. The Number of Completed Sub-Operations (0000,1021), Number of Warning Sub-Operations (0000,1023), Number of Failed Sub-Operations (0000,1022) can be used to obtain more detailed information. + + + + + The SCU may cancel the C-GET operation by issuing a C-GET-CANCEL request at any time during the processing of the C-GET request. A C-GET response with a status of Canceled shall indicate to the SCU that the retrieve was canceled. Optionally, the C-GET response with a status of Canceled shall indicate the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-GET-CANCEL request. + + + The SCP of the Storage Service Class shall not return a status of "Error: Data set does not match SOP Class" (A9xx) or "Warning: Data set does not match SOP Class" (B007) due to the absence of the Attributes described in . + + +
+
+ Extended Behavior of SCU + The extended behavior of the SCU shall be as specified in , except that Relational-retrieve shall not be supported. +
+
+
+ C-GET SCP and C-STORE SCU Behavior +
+ Baseline Behavior of SCP + An SCP conveys the following semantics with a C-GET response: + + + The SCP shall identify a set of Entities at the level of the transfer based upon the values in the Unique Keys in the Identifier of the C-GET request. + + + The SCP shall initiate C-STORE sub-operations for the identified SOP Instances, but shall not include in this C-STORE sub-operation the Attributes described in section . The SCP of the Query/Retrieve Service Class shall serve as an SCU of the Storage Service Class. + + + Apart from the Attributes listed in section , the SOP Instance sent via the C-STORE sub-operation shall be unchanged, and no corresponding changes to other Attributes shall be made. + + + + In particular, the Study, Series and SOP Instance UIDs and SOP Class UID will not be altered. + + + + The SCP shall initiate C-STORE sub-operations over the same Association for all SOP Instances specified in the C-GET request. + + + A sub-operation is considered a Failure if the SCP is unable to initiate a C-STORE sub-operation because the Query/Retrieve SCU did not offer an appropriate presentation context for a given stored SOP Instance. + + + Optionally, the SCP may generate responses to the C-GET with status equal to Pending during the processing of the C-STORE sub-operations. These responses shall indicate the number of Remaining, Completed, Failure, and Warning C-STORE sub-operations. + + + When the number of Remaining sub-operations reaches zero, the SCP shall generate a final response with a status equal to Success, Warning or Failed. The status contained in the C-GET response shall contain: + + + Success if all sub-operations were successfully completed + + + Failure if all sub-operations were unsuccessful + + + Warning in all other cases. + + + + + The SCP may receive a C-GET-CANCEL request at any time during the processing of the C-GET request. The SCP shall interrupt all C-STORE sub-operation processing and return a status of Canceled in the C-GET response. The C-GET response with a status of Canceled shall contain the number of Completed, Failed, and Warning C-STORE sub-operations. If present, the Remaining sub-operations count shall contain the number of C-STORE sub-operations that were not initiated due to the C-GET-CANCEL request. + + + If the SCP manages images in multiple alternate encodings (see ), only one of the alternate encodings of an image shall be used as the existing SOP Instance from which frames are to be extracted. + + +
+
+ Extended Behavior of SCP + The extended behavior of the SCP shall be as specified in , except that Relational-retrieve shall not be supported. +
+
+
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. AEs supporting DICOM Query/Retrieve SOP Classes utilize Association establishment negotiation by defining the use of Application Association Information. See for an overview of Association negotiation. + SOP Classes of the Composite Instance Retrieve Without Bulk Data Service, which include retrieval services based on the C-GET operation, use the SCP/SCU Role Selection Sub-Item to identify the SOP Classes that may be used for retrieval. +
+ Association Negotiation for C-GET SOP Classes + Rules are as specified in , except that the extended negotiation sub-item, if used, shall be used as defined in . + + + + Though converted images may be specified by their SOP Instance UID in the Request Identifier, which is always at the instance level, there remains a need for extended negotiation and specification of the Query/Retrieve View in order to assure that referential integrity is maintained within the returned SOP Instances (e.g., that a reference to a SOP Instance UID is to a converted image or not, as appropriate). + + + Relational-retrieval is not applicable to this SOP Class, hence the Extended Negotiation Sub-Item does not include the use of that byte. + + + +
+
+
+ SOP Class Definitions +
+ Composite Instance Retrieve Without Bulk Data SOP Class Group + In the Composite Instance Retrieve Without Bulk Data Only Information Model, only a single Retrieve Level is used. + + + + + + + + + + + + + + +
Retrieve Level Value for Retrieve Without Bulk Data
+ + Retrieve Level + + + + Value in (0008,0052) + +
+ Composite Instance + + IMAGE +
+ + The use of the word "IMAGE" rather than "Composite Instance" is historical to allow backward compatibility with previous versions of the standard. It should not be taken to mean that Composite Instances of other than image type are not included at the level indicated by the value IMAGE. + +
+ Composite Instance Retrieve Without Bulk Data Information Model +
+ E/R Model + The Composite Instance Retrieve Without Bulk Data Only Information Model has only a single level: IMAGE. +
+
+ Composite Instance Level + + defines the keys at the Composite Instance level of the Composite Instance Retrieve Without Bulk Data Query/Retrieve Information model. + + + + + + + + + + + + + + + + +
Composite Instance Level Keys for the Composite Instance Root Query/Retrieve Information Model
+ + Attribute Name + + + + Tag + + + + Matching Key Type + +
+ SOP Instance UID + + (0008,0018) + + U +
+
+
+ Scope of the C-GET Commands and Sub-Operations + A C-GET request may only be performed at the IMAGE level of the Query/Retrieve Model. A C-GET indicates that selected individual Composite Instances, without bulk data Attributes shall be transferred. +
+
+
+ Conformance Requirements + An implementation may conform to one of the SOP Classes of the Composite Instance Retrieve Without Bulk Data SOP Class Group as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation that conforms to one of the SOP Classes of the Composite Instance Retrieve Without Bulk Data SOP Class Group as an SCU shall support retrievals against the Query/Retrieve Information Model described in using the C-GET SCU Behavior described in . An implementation that conforms to one of the SOP Classes of the Composite Instance Retrieve Without Bulk Data SOP Class Group as an SCU, and that generates retrievals using the C-GET operation, shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+ SCP Conformance + An implementation that conforms to one of the SOP Classes of the Composite Instance Retrieve Without Bulk Data SOP Class Group as an SCP shall support retrievals against both levels of the Retrieve Information Model described in using the C-GET SCP Behavior described in . An implementation that conforms to one of the SOP Classes of the Composite Instance Retrieve Without Bulk Data SOP Class Group as an SCP, and that satisfies retrievals using the C-GET operation, shall state in its Conformance Statement the Storage Service Class SOP Classes under which it shall support the C-STORE sub-operations generated by the C-GET. +
+
+
+ SOP Classes + The SOP Classes in the Composite Instance Retrieve Without Bulk Data SOP Class Group of the Query/Retrieve Service Class identify the Composite Instance Retrieve Without Bulk Data Only Information Model, and the DIMSE-C operations supported. The Standard SOP Classes are listed in . + + + + + + + + + + + + + + +
SOP Classes for Composite Instance Query/Retrieve Root
+ + SOP Class Name + + + + SOP Class UID + +
+ Composite Instance Retrieve Without Bulk Data - GET + + 1.2.840.10008.5.1.4.1.2.5.3 +
+
+
+
+
+ + Ophthalmic Refractive Measurements Storage SOP Classes(Normative) +
+ Scope + Refractive instruments are the most commonly used instruments in eye care. At present many of them have the capability for digital output, but their data is most often addressed by manual input into a paper or electronic record. Lensometry, Autorefraction, Keratometry, Subjective Refraction, and Visual Acuity Measurements SOP Classes support devices such as lensometers, auto-refractors, keratometers, autophoropters, and autoprojectors. +
+
+ Behavior of a SCP + For a device that is both a SCU and a SCP of these Storage SOP Classes, in addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for Structured Reporting Storage SOP Classes: + + + A SCP of these SOP Class shall support Level 2 Conformance as defined in . + + + + This requirement means that all Type 1, Type 2, and Type 3 Attributes defined in the Information Object Definition and Private Attributes associated with the SOP Class will be stored and may be accessed. + +
+
+ + Implant Template Query/Retrieve Service Classes +
+ Overview +
+ Scope + The Implant Template Query/Retrieve Service Classes define application-level classes-of-service that facilitate access to Implant Template and Implant Assembly Template composite objects. +
+
+ Conventions + Key Attributes serve two purposes; they may be used as Matching Key Attributes or as Return Key Attributes. Matching Key Attributes may be used for matching (criteria to be used in the C-FIND request to determine whether an entity matches the query). Return Key Attributes may be used to specify desired return Attributes (what elements in addition to the Matching Key Attributes have to be returned in the C-FIND response). + + Matching Keys are typically used in an SQL 'where' clause. Return Keys are typically used in an SQL 'select' clause to convey the Attribute values. + + Matching Key Attributes may be of Type "required" (R) or "optional" (O). Return Key Attributes may be of Type 1, 1C, 2, 2C, 3 as defined in . +
+
+ Query/Retrieve Information Model + In order to serve as an SCP of the Implant Template Query/Retrieve Service Class, a DICOM AE possesses information about the Attributes of a number of Implant Template or Implant Assembly Template composite SOP Instances. The information is organized into an Information Model. The Information Models for the different SOP Classes specified in this Annex are defined in . +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of an Implant Template or Implant Assembly Template Query/Retrieve Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Implant Template and Implant Assembly Template Query/Retrieve Service Classes are implemented using the DIMSE-C C-FIND, C-MOVE and C-GET services as defined in . + An SCP of this SOP Class shall support Level-2 conformance as defined in . + The semantics of the C-FIND service are the same as those defined in the Service Definition of the Basic Worklist Management Service Class. + The semantics of the C-MOVE service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. + The semantics of the C-GET service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. +
+
+
+ Implant Template Information Models Definitions + The Implant Template, Implant Assembly Template, and Implant Template Group Information Models are identified by the SOP Class negotiated at Association establishment time. Each SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + The Implant Template, Implant Assembly Template, and Implant Template Group Information Models are defined in , with the Entity-Relationship Model Definition and Key Attributes Definition analogous to those defined in the Worklist Information Model Definition of the Basic Worklist Management Service. +
+
+ Implant Template Information Models + The Implant Template Information Models are based upon a one level entity: + + + Implant Template object instance. + + + The Implant Template object instance contains Attributes associated with the Implant Template object IE of the Composite IODs as defined in . + The Implant Assembly Template Information Model is based upon a one level entity: + + + Implant Assembly Template object instance. + + + The Implant Assembly Template object instance contains Attributes associated with the Implant Assembly Template object IE of the Composite IODs as defined in . + The Implant Assembly Group Information Model is based upon a one level entity: + + + Implant Template Group object instance. + + + The Implant Template Group object instance contains Attributes associated with the Implant Template Group object IE of the Composite IODs as defined in . +
+
+ DIMSE-C Service Groups +
+ C-FIND Operation + See the C-FIND Operation definition for the Basic Worklist Management Service Class (K.4.1), and substitute "Implant Template" for "Worklist". The "Worklist" Search Method shall be used. + The SOP Class UID identifies the Implant Template or Implant Assembly Template, respectively Information Model against which the C-FIND is to be performed. The Key Attributes and values allowable for the query are defined in the SOP Class definitions for the Implant Template and Implant Assembly Template Information Model. +
+ Service Class User Behavior + When receiving several Implant Template Instances with the same Implant Part Number, the receiving application shall use Effective DateTime (0068,6226) to determine the appropriate Instance. +
+
+ Service Class Provider Behavior + An SCP of this SOP Class shall support Level-2 conformance as defined in . +
+
+
+ C-MOVE Operation + See the C-MOVE Operation definition for the Query/Retrieve Service Class (C.4.2). No Extended Behavior or Relational-Retrieve is defined for the Implant Template and Implant Assembly Template Query/Retrieve Service Classes. + Query/Retrieve Level (0008,0052) is not relevant to the Implant Template and Implant Assembly Template Query/Retrieve Service Classes, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+ C-GET Operation + See the C-GET Operation definition for the Query/Retrieve Service Class (C.4.2). No Extended Behavior or Relational-Retrieve is defined for the Implant Template and Implant Assembly Template Query/Retrieve Service Classes. + + More than one entity may be retrieved, using List of UID matching. + +
+
+
+ Association Negotiation + See the Association Negotiation definition for the Basic Worklist Management Service Class (K.5). +
+
+ SOP Class Definitions +
+ Implant Template Information Model +
+ E/R Models + The Implant Template Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Implant Template Instance. + +
+ Implant Template Information Model E/R Diagram + + + + + + +
+
+ The Implant Assembly Template Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Implant Assembly Template Instance. + +
+ Implant Assembly Template Information Model E/R Diagram + + + + + + +
+
+ The Implant Template Group Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Implant Template Group Instance. + +
+ Implant Template Group Information Model E/R Diagram + + + + + + +
+
+
+
+ Implant Template Attributes +
+ Generic Implant Template Attributes + + defines the Attributes of the Generic Implant Template Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Implant Template Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + +
+ + Implant Template + +
+ Manufacturer + + (0008,0070) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Implant Name + + (0022,1095) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Implant Size + + (0068,6210) + + R + + 2 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Implant Part Number + + (0022,1097) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Replaced Implant Template Sequence + + (0068,6222) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Derivation Implant Template Sequence + + (0068,6224) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Effective DateTime + + (0068,6226) + + R + + 1 + + Shall be retrieved with Single Value or Range Matching. +
+ Original Implant Template Sequence + + (0068,6225) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Implant Target Anatomy Sequence + + (0068,6230) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Anatomic Region Sequence + + (0008,2218) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ Implant Regulatory Disapproval Code Sequence + + (0068,62A0) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Material Code Sequence + + (0068,63A0) + + R + + 1 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Coating Materials Code Sequence + + (0068,63A4) + + R + + 1 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+
+
+ Implant Assembly Template Attributes + + defines the Attributes of the Implant Assembly Template Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Implant Assembly Template Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + +
+ + Implant Assembly Template + +
+ Implant Assembly Template Name + + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Manufacturer + + (0008,0070) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Procedure Type Code Sequence + + (0076,0020) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Replaced Implant Assembly Template Sequence + + (0076,0008) + + R + + 1 + + Shall be retrieved with Sequence or Universal Matching. +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Original Implant Assembly Template Sequence + + (0076,000C) + + R + + 1 + + Shall be retrieved with Sequence or Universal Matching. +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Derivation Implant Assembly Template Sequence + + (0076,000E) + + R + + 1 + + Shall be retrieved with Sequence or Universal Matching. +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Surgical Technique + + (0076,0030) + + R + + 2 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+
+
+ Implant Template Group Attributes + + defines the Attributes of the Implant Template Group Information Model: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Implant Template Group Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + +
+ + Implant Template Group + +
+ Implant Template Group Name + + (0078,0000) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Implant Template Group Description + + (0078,0010) + + - + + 2 + +
+ Implant Template Group Issuer + + (0078,0020) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Effective DateTime + + (0068,6226) + + R + + 1 + + Shall be retrieved with Single Value or Range Matching. +
+ Replaced Implant Template Group Sequence + + (0078,0026) + + R + + 2 + + Shall be retrieved with Sequence or Universal Matching +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+
+
+
+ Conformance Requirements + An implementation may conform to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCU, SCP, any combination of two of these, or all three. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes shall support queries against the appropriate Information Model using the C-FIND SCU Behavior described for the Basic Worklist Management Service Class (see and ). + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCU shall state in its Conformance Statement whether it requests Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCU shall support transfers against the appropriate Information Model, using the C-MOVE SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+ C-GET SCU Conformance + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCU shall support transfers against the appropriate Information Model, using the C-GET SCU baseline behavior described for the Query/Retrieve Service Class (see ). +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCP shall support queries against the appropriate Template Information Model, using the C-FIND SCP Behavior described for the Basic Worklist Management Service Class (see ). + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCP shall state in its Conformance Statement whether it supports Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching and encoding responses. +
+
+ C-MOVE SCP Conformance + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCP shall support transfers against the appropriate Information Model, using the C-MOVE SCP baseline behavior described for the Query/Retrieve Service Class (see ). + An implementation that conforms to one of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Classes as an SCP, which generates transfers using the C-MOVE operation, shall state in its Conformance Statement appropriate Storage Service Class, under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to one of the SOP Classes of the Implant Template, Implant Assembly Template, or Implant Template Group Information Model SOP Class Group as an SCP shall support retrievals against the Query/Retrieve Information Model described in using the C-GET SCP Behavior described in . +
+
+
+
+ SOP Classes + The SOP Classes of the Implant Template Information Models in the Implant Template Query/Retrieve Service Class identify the Implant Template Information Models, and the DIMSE-C operations supported. The SOP Classes of the Implant Assembly Template Information Models in the Implant Assembly Template Query/Retrieve Service Class identify the Implant Assembly Template Information Models, and the DIMSE-C operations supported. The SOP Classes of the Implant Template Group Information Models in the Implant Template Group Query/Retrieve Service Class identify the Implant Template Group Information Models, and the DIMSE-C operations supported. The following Standard SOP Classes are identified: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Implant Template SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Generic Implant Template Information Model - FIND + + 1.2.840.10008.5.1.4.43.2 +
+ Generic Implant Template Information Model - MOVE + + 1.2.840.10008.5.1.4.43.3 +
+ Generic Implant Template Information Model - GET + + 1.2.840.10008.5.1.4.43.4 +
+ Implant Assembly Template Information Model - FIND + + 1.2.840.10008.5.1.4.44.2 +
+ Implant Assembly Template Information Model - MOVE + + 1.2.840.10008.5.1.4.44.3 +
+ Implant Assembly Template Information Model - GET + + 1.2.840.10008.5.1.4.44.4 +
+ Implant Template Group Information Model - FIND + + 1.2.840.10008.5.1.4.45.2 +
+ Implant Template Group Information Model - MOVE + + 1.2.840.10008.5.1.4.45.3 +
+ Implant Template Group Information Model - GET + + 1.2.840.10008.5.1.4.45.4 +
+
+
+
+
+ + Unified Procedure Step Service and SOP Classes (Normative) +
+ Overview + This Annex defines the Service and SOP Classes associated with a Unified Worklist and Procedure Step. + The Unified Procedure Step Service Class provides for management of simple worklists, including creating new worklist items, querying the worklist, and communicating progress and results. + A worklist is a list of Unified Procedure Step (UPS) instances. Each UPS instance unifies the worklist details for a single requested procedure step together with the result details of the corresponding performed procedure step. There is a one to one relationship between the procedure step request and the procedure step performed. + Unified Procedure Step instances may be used to represent a variety of scheduled tasks such as: Image Processing, Quality Control, Computer Aided Detection, Interpretation, Transcription, Report Verification, or Printing. + The UPS instance can contain details of the requested task such as when it is scheduled to be performed or Workitem Codes describing the requested actions. The UPS may also contain details of the input information the performer needs to do the task and the output the performer produced, such as: Current Images, Prior Images, Reports, Films, Presentation States, or Audio recordings. + The Unified Worklist and Procedure Step Service Class includes four SOP Classes associated with UPS instances. The SOP Class UID for any UPS Instance always specifies the UPS Push SOP Class. The separate SOP Classes facilitate better negotiation and logical implementation groups of functionality. + The UPS Push SOP Class allows an SCU to instruct the SCP to create a new UPS instance, effectively letting a system push a new work item onto the SCP's worklist. It is important to note that the SCP could be a Worklist Manager that maintains the worklist for other systems that will perform the work, or the SCP could be a performing system itself that manages an internal worklist. + The UPS Pull SOP Class allows an SCU to query a Worklist Manager (the SCP) for matching UPS instances, and instruct the SCP to update the status and contents of selected items (UPS instances). The SCU effectively pulls work instructions from the worklist. As work progresses, the SCU records details of the activities performed and the results created in the UPS instance. + The UPS Watch SOP Class allows an SCU to subscribe for status update events and retrieve the details of work items (UPS instances) managed by the SCP. + The UPS Event SOP Class allows an SCP to provide the actual status update events for work items it manages to relevant (i.e., subscribed) SCUs. + Each of these services has an equivalent HTTP operation defined by the UPS-RS Worklist Service (see ). + While a Unified Worklist and Procedure Step Service Class SCP is not required to support UPS-RS, an SCP may choose to support one or more of the UPS-RS services as an Origin-Server. In this scenario, an SCP/Origin Server shall follow the same internal behavior for all Workitems irrespective of whether they originated with a DIMSE request or an HTTP request. A DIMSE request and its equivalent HTTP request with the same parameters shall yield the same response. + For example: + + + A Workitem instance created via DIMSE N-CREATE can be retrieved via HTTP requests and vice-versa + + + A Workitem instance created via DIMSE N-CREATE can be updated, have its state changed or be canceled via HTTP requests and vice-versa + + + A C-FIND request and an HTTP SearchForUPS request with the same parameters shall return the same set of results + + + An N-EVERT-REPORT SCU that also supports HTTP subscriptions will record whether a given subscriber uses DIMSE or WebSockets and send the appropriate form of notification to that subscriber + + + A change made to a Workitem instance will result in the same event notifications regardless of whether the change was requested via DIMSE or HTTP + + + A Global Subscription request or a Filtered Global Subscription request will subscribe an SCU (or User-Agent) to instances created both via DIMSE and via HTTP requests + + + A DIMSE event subscriber will receive notifications for relevant changes made via HTTP requests + + + An HTTP event subscriber will receive notifications for relevant changes made via DIMSE requests + + + The mapping between UPS DIMSE operations and UPS-RS services is defined in . +
+ Unified Procedure Step States + + , and specify how changes in the state of a Unified Procedure Step shall be managed. + +
+ Unified Procedure Step State Diagram + + + + + + +
+
+ The following interactions represent an example sequence of events and state transitions. Observe that the DIMSE Services described here operate on the same IOD. The multiple UPS SOP Classes thus act in a coordinated manner as specified in this Annex. + To create a UPS, an SCU uses an N-CREATE to push a UPS onto the SCP's worklist. The SCP responds to such requests by creating a Unified Procedure Step (UPS) with an initial state of SCHEDULED. + + All UPS Instances are instances of the UPS Push SOP Class, although the other three SOP Classes (UPS Pull, UPS Watch and UPS Event) may also operate on the Instance. + + To subscribe to receive N-EVENT-REPORTs for a UPS, or to unsubscribe to stop receiving N-EVENT-REPORTS, an SCU uses an N-ACTION request. The SCU may be the system that created the UPS as a Push SCU, or may be some other system with a reason to track the progress and results of a scheduled step. + To inform interested systems of the state of a UPS or the SCP itself, an SCP issues N-EVENT-REPORTs to SCUs that have subscribed. + To find a UPS of interest, an SCU uses a C-FIND to query the SCP for relevant UPS instances. + To "claim" and start work on a UPS, an SCU (which will be referred to here as the "Performing SCU") uses an N-ACTION Change State request to set the UPS state to IN PROGRESS and provide a transaction UID (which will be referred to here as the Locking UID). For a SCHEDULED UPS, the SCP responds by changing the UPS state to IN PROGRESS and recording the transaction UID for future use. For a UPS with other status, the SCP rejects the request. + The SCP does not permit the status of a SCHEDULED UPS to be set to COMPLETED or CANCELED without first being set to IN PROGRESS. + To modify details of the performed procedure, the Performing SCU uses an N-SET request to the SCP (providing the Locking UID for the UPS). N-SET requests on an IN PROGRESS UPS where the Locking UID in the N-SET data set does not match the Locking UID in the UPS are rejected by the SCP. + To modify the status of the procedure step, the Performing SCU uses an N-ACTION Change State request to the SCP (providing the Locking UID for the UPS). N-ACTION Change State requests where the Locking UID in the N-ACTION data set does not match the Locking UID in the UPS are rejected by the SCP. + The Locking UID effectively limits control of the state of an IN PROGRESS UPS to only the SCP and the Performing SCU. The SCP does not check whether IP addresses, AE-Titles, or parameters other than the Locking UID match to determine if the SCU has permission. + When the Performing SCU completes work on the UPS, it N-SETs any values necessary to meet the Final State requirements in , then uses an N-ACTION request (providing the Locking UID for the UPS during both steps) for the SCP to change the UPS state to COMPLETED. + When the Performing SCU abandons work on an incomplete UPS, it N-SETs any values necessary to meet the Final State requirements in , then uses an N-ACTION request (providing the Locking UID for the UPS) for the SCP to change the UPS state to CANCELED. + To request cancellation of a UPS, non-performing SCUs use an N-ACTION Request Cancel (see and for example cases). + + + If the UPS is still in the SCHEDULED state, the SCP first changes the UPS state to IN PROGRESS, and then to CANCELED, issuing the appropriate N-EVENT-REPORTS. + + + If the UPS is already IN PROGRESS and the SCP is itself performing the UPS, it may, at its own discretion, choose to cancel the UPS as described in the previous paragraph. + + + If the UPS is already IN PROGRESS and the SCP is not the performer, it does not change the UPS state to CANCELED, but rather responds by issuing an N-EVENT-REPORT of the cancellation request to all subscribed SCUs. If the Performing SCU is listening to N-EVENT-REPORTs it may, at its own discretion, choose to cancel the UPS as described above. + + + + describes the valid UPS states + + + + + + + + + + + + + + + + + + + + + + + + + + +
Unified Procedure Step (UPS) States
+ + State + + + + Description + +
+ SCHEDULED + + The UPS is scheduled to be performed. +
+ IN PROGRESS + + The UPS has been claimed and a Locking UID has been set. Performance of the UPS has likely started. +
+ CANCELED + + The UPS has been permanently stopped before or during performance of the step. This may be due to voluntary or involuntary action by a human or machine. Any further UPS-driven work required to complete the scheduled task must be performed by scheduling another (different) UPS. +
+ COMPLETED + + The UPS has been completed. +
+ COMPLETED and CANCELED are "Final States" that involve specific requirements on the UPS as described in . + + describes the valid state transitions (a row in the table defines what should happen in response to a certain event for each initial state). Details on how the Operations listed in the table should be carried out are described in section . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Unified Procedure Step State Transition Table
+ + States +
+ + Events + + + + + null + + + + + SCHEDULED + + + + IN PROGRESS + + + + COMPLETED + + + + CANCELED + +
+ N-CREATE received for this SOP Instance UID + + Create SOP Instance with empty transaction UID, Change State to SCHEDULED + + error + 0111 + + error + 0111 + + error + 0111 + + error + 0111 +
+ N-ACTION to Change State to IN PROGRESS with correct transaction UID + + error + C307 + + Report state change, Record transaction UID, Change State to IN PROGRESS + + error + C302 + + error + C300 + + error + C300 +
+ N-ACTION to Change State to IN PROGRESS without correct transaction UID + + error + C307 + + error + C301 + + error + C301 + + error + C301 + + error + C301 +
+ N-ACTION to Change State to SCHEDULED + + error + C307 + + error + C303 + + error + C303 + + error + C303 + + error + C303 +
+ N-ACTION to Change State to COMPLETED, with correct transaction UID + + error + C307 + + error + C310 + + If Final State Requirements met, (Report state change, Change State to COMPLETED); Else C304 + + warning + B306 + + error + C300 +
+ N-ACTION to Change State to COMPLETED, without correct transaction UID + + error + C307 + + error + C301 + + error + C301 + + error + C301 + + error + C301 +
+ N-ACTION to Request Cancel + + error + C307 + + Report state change to IN-PROGRESS, Report state change to CANCELED, Change State to CANCELED + + Report that an Application Entity requested a cancel. + + error + C311 + + warning + B304 +
+ N-ACTION to Change State to CANCELED, with correct transaction UID + + error + C307 + + error + C310 + + If Final State Requirements met, (Report state change, Change State to CANCELED); Else C304. + + error + C300 + + warning + B304 +
+ N-ACTION to Change State to CANCELED, without correct transaction UID + + error + C307 + + error + C301 + + error + C301 + + error + C301 + + error + C301 +
+
+
+
+ DIMSE Service Groups + The DIMSE Services shown in , , and are applicable to the Unified Procedure Step (UPS) IOD under the UPS Push, UPS Pull, UPS Watch and UPS Event SOP Classes respectively. + + + + + + + + + + + + + + + + + + + + + + +
DIMSE Service Group - UPS Push
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-ACTION - Request UPS Cancel + + U/M +
+ N-GET + + U/M +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
DIMSE Service Group - UPS Pull
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ C-FIND + + M/M +
+ N-GET + + M/M +
+ N-SET + + M/M +
+ N-ACTION - Change UPS State + + M/M +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
DIMSE Service Group - UPS Watch
+ + DIMSE Service Element + + + + SCU/SCP + +
+ N-ACTION - Un/Subscribe + + M/M +
+ N-GET + + M/M +
+ C-FIND + + U/M +
+ N-ACTION - Request UPS Cancel + + U/M +
+ + + + + + + + + + + + + + +
DIMSE Service Group - UPS Event
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-EVENT-REPORT + + M/M +
+
+ Change UPS State (N-ACTION) + This operation allows an SCU to ask the SCP to change the state of a Unified Procedure Step (UPS) instance. This operation shall be invoked by the SCU through the DIMSE N-ACTION Service. +
+ Action Information + DICOM AEs that claim conformance to the UPS Pull SOP Class as an SCU and/or an SCP shall support the Action Types and Action Information as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + +
Change UPS State - Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Change UPS State + + 1 + + Procedure Step State + + (0074,1000) + + 1/1 +
+ Transaction UID + + (0008,1195) + + 1/1 +
+
+
+ Service Class User Behavior + An SCU uses N-ACTION to ask the SCP to change the state of a UPS Instance as shown in . Since all UPSs are created as instances of the UPS Push SOP Class, the Requested SOP Class UID (0000,0003) in the N-ACTION request shall be the UID of the UPS Push SOP Class. See for further details. + To take control of a SCHEDULED UPS, an SCU shall generate a Transaction UID and submit a state change to IN PROGRESS including the Transaction UID in the submission. The SCU shall record and use the Transaction UID in future N-ACTION and N-SET requests for that UPS instance. + + + + The performing SCU may wish to record the Transaction UID in non-volatile storage. This would allow the SCU to retain control over the UPS after recovering from a crash. + + + If two SCUs try to take control of a UPS, the second SCU will get an error since the first SCU established the correct Transaction UID, so the Transaction UID provided by the second SCU is incorrect. + + + + Upon completion of an IN PROGRESS UPS it controls, an SCU shall submit a state change to COMPLETED and include the Transaction UID for the UPS instance. + To cancel an IN PROGRESS UPS for which it has the Transaction UID, an SCU shall submit a state change to CANCELED and include the Transaction UID for the UPS instance. + + + + Prior to submitting the state change to CANCELED, the performing SCU can N-SET the values of Reason For Cancellation, Procedure Step Discontinuation Reason Code Sequence, Contact Display Name or Contact URI to provide information to observing SCUs about the context of the cancellation. + + + To request cancellation of an IN PROGRESS UPS for which it does not have the Transaction UID, an SCU uses the Request UPS Cancel action as described in , rather than a Change UPS State action. + + + + Prior to submitting a state change to COMPLETED or CANCELED for a UPS instance it controls, the SCU shall perform any N-SETs necessary for the UPS to meet Final State requirements as described in section . + At any time after receipt of the N-ACTION-Response, the SCU may release the association on which it sent the N-ACTION-Request. +
+
+ Service Class Provider Behavior + The SCP shall perform the submitted state change for the identified UPS instance by setting the Procedure Step State (0074,1000) to the requested value, or shall report the appropriate failure response code. + Upon successfully changing the state of a UPS instance to IN PROGRESS, the SCP shall record the Transaction UID provided by the SCU in the Transaction UID (0008,1195) of the UPS instance. + Upon completion of the N-ACTION request, the SCP shall return, via the N-ACTION response primitive, the N-ACTION Status Code applicable to the associated request as shown in . + The SCP shall only perform legal state changes as described in . + The SCP shall refuse requests to change the state of an IN PROGRESS UPS unless the Transaction UID of the UPS instance is provided in the request. + The SCP shall refuse requests to change the state of an IN PROGRESS UPS to COMPLETED or CANCELED if the Final State requirements described in have not been met. + After the state of the UPS instance has been changed to COMPLETED or CANCELED, the SCP shall not delete the instance until all deletion locks have been removed. + + See for a description of how SCUs place and remove deletion locks and see Reliable Watchers and Deletion Locks for further discussion. + + The SCP may also modify the Procedure Step State (0074,1000) of a UPS instance independently of an N-ACTION request, e.g., if the SCP is performing the procedure step itself, or if it has been determined that the performing SCU has been disabled. + + If the SCP is not performing the procedure step, this should be done with caution. + + Upon successfully changing the state of a UPS instance, the SCP shall carry out the appropriate N-EVENT-REPORT behavior as described in if it supports the UPS Event SOP Class as an SCP. + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. Further requiring or documenting authentication and/or authorization features from the SCU or SCP is beyond the scope of this SOP Class. +
+
+ Status Codes + The status values that are specific for this DIMSE operation are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + The requested state change was performed + + 0000 +
+ Warning + + The UPS is already in the requested state of CANCELED + + B304 +
+ The UPS is already in the requested state of COMPLETED + + B306 +
+ Failure + + Refused: The UPS may no longer be updated + + C300 +
+ Refused: The correct Transaction UID was not provided + + C301 +
+ Refused: The UPS is already IN PROGRESS + + C302 +
+ Refused: The UPS may only become SCHEDULED via N-CREATE, not N-SET or N-ACTION + + C303 +
+ Refused: The UPS has not met final state requirements for the requested state change + + C304 +
+ Specified SOP Instance UID does not exist or is not a UPS Instance managed by this SCP + + C307 +
+ Refused: The UPS is not yet in the "IN PROGRESS" state + + C310 +
+
+
+
+ Request UPS Cancel (N-ACTION) + This operation allows an SCU that does not control a given Unified Procedure Step (UPS) instance to request to the SCP that the instance be canceled. This operation shall be invoked by the SCU through the DIMSE N-ACTION Service. +
+ Action Information + DICOM AEs that claim conformance to the UPS Push SOP Class as an SCU or an SCP shall support the Action Types and Action Information as specified in . DICOM AEs that claim conformance to the UPS Watch SOP Class as an SCP or claim conformance to the UPS Watch SOP Class as an SCU and choose to implement Request UPS Cancel shall support the Action Types and Action Information as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Request UPS Cancel - Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Request UPS Cancel + + 2 + + Reason For Cancellation + + (0074,1238) + + 3/1 +
+ Procedure Step Discontinuation Reason Code Sequence + + (0074,100e) + + 3/1 +
+ >Code Value + + (0008,0100) + + 1/1 +
+ >Coding Scheme Designator + + (0008,0102) + + 1/1 +
+ >Coding Scheme Version + + (0008,0103) + + 1C/1C + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously +
+ >Code Meaning + + (0008,0104) + + 1/1 +
+ Contact URI + + (0074,100a) + + 3/1 +
+ Contact Display Name + + (0074,100c) + + 3/1 +
+
+
+ Service Class User Behavior + An SCU uses N-ACTION to request to the SCP that the state of a UPS Instance be changed to CANCELED as shown in . Since all UPSs are created as instances of the UPS Push SOP Class, the Requested SOP Class UID (0000,0003) in the N-ACTION request shall be the UID of the UPS Push SOP Class. See for further details. + The SCU may include a Reason For Cancellation and/or a proposed Procedure Step Discontinuation Reason Code Sequence. + The SCU may also provide a Contact Display Name and/or a Contact URI for the person with whom the cancel request may be discussed. + + An N-ACTION Status Code indicating success means that the Request was accepted, not that the UPS has been canceled. The system performing the UPS is not obliged to honor the request to cancel and in some scenarios, may not even receive notification of the request. See . + + At any time after receipt of the N-ACTION-Response, the SCU may release the association on which it sent the N-ACTION-Request. + To cancel an IN PROGRESS UPS that the SCU is itself performing, the SCU shall instead use the Change UPS State action as described in . +
+
+ Service Class Provider Behavior + The SCP shall send appropriate "UPS Cancel Requested" N-EVENT-REPORT messages, as described in or shall report the appropriate failure response code. + + If provided, the Reason For Cancellation, a proposed Procedure Step Discontinuation Reason Code Sequence, a Contact Display Name and a Contact URI of someone responsible for the Cancel request might be useful in deciding to cancel the UPS or might be displayed to an operator so they can make contact for the purpose of clarifying or confirming the Cancel request. If the SCP is the performer and chooses to actually Cancel the UPS, it may at its own discretion set the Procedure Step Discontinuation Reason Code Sequence in the UPS instance based on the corresponding values provided. + + If the Procedure Step State (0074,1000) of the UPS instance is still SCHEDULED, the SCP shall change the Procedure Step State, as described in , first to IN PROGRESS and then to CANCELED, ensuring that the Final State requirements, described in section , are met. + If the Procedure Step State (0074,1000) of the UPS instance is IN PROGRESS, and the SCP is itself the performer of the UPS, the SCP may, at its own discretion, choose to cancel the UPS as described in . + If the SCP is the performer of the UPS and chooses not to cancel, or if there is no possibility that the performing SCU will be informed of the cancel request (e.g., the subscription list for the UPS is empty, or the SCP has determined that the performing SCU has been disabled), the SCP may return a failure. + Upon completion of the N-ACTION request, the SCP shall return, via the N-ACTION response primitive, the N-ACTION Status Code applicable to the associated request as shown in . + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. Further requiring or documenting authentication and/or authorization features from the SCU or SCP is beyond the scope of this SOP Class. +
+
+ Status Codes + The status values that are specific for this DIMSE operation are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + The cancel request is acknowledged + + 0000 +
+ Warning + + The UPS is already in the requested state of CANCELED + + B304 +
+ Failure + + Refused: The UPS is already COMPLETED + + C311 +
+ Refused: Performer chooses not to cancel + + C313 +
+ Specified SOP Instance UID does not exist or is not a UPS Instance managed by this SCP + + C307 +
+ Refused: The performer cannot be contacted + + C312 +
+
+
+
+ Subscribe/Unsubscribe to Receive UPS Event Reports (N-ACTION) + This operation allows an SCU to subscribe with an SCP in order to receive N-EVENT-REPORTS of subsequent changes to the state of a UPS instance, or to unsubscribe in order to no longer receive such N-EVENT-REPORTs. This operation shall be invoked by the SCU through the DIMSE N-ACTION Service. +
+ Action Information + DICOM AEs that claim conformance to the UPS Watch SOP Class as an SCU and/or an SCP shall support the Action Types and Action Information as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Subscribe/Unsubscribe to Receive UPS Event Reports - Action Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Requirement Type SCU/SCP + +
+ Subscribe to Receive UPS Event Reports + + 3 + + Receiving AE + + (0074,1234) + + 1/1 +
+ Deletion Lock + + (0074,1230) + + 1/1 +
+ Match Keys (see ) + + 1/1 +
+ Unsubscribe from Receiving UPS Event Reports + + 4 + + Receiving AE + + (0074,1234) + + 1/1 +
+ Suspend Global Subscription + + 5 + + Receiving AE + + (0074,1234) + + 1/1 +
+ Each AE may be in one of three UPS Subscription States for each existing UPS Instance: Not Subscribed, Subscribed with Deletion Lock, or Subscribed w/o Deletion Lock. The UPS Subscription State determines whether N-EVENT-REPORTs relating to a UPS Instance will be sent to the AE. + Each AE may also be in one of three Global Subscription States for a given SCP: No Global Subscription, Globally Subscribed with Deletion Lock, Globally Subscribed w/o Deletion Lock. The Global Subscription State mainly determines the initial UPS Subscription State for an AE and new UPS Instances created by the SCP. Changes to the Global Subscription State can also change the UPS Subscription State for existing UPS Instances as described in . + The three Subscription actions in are used to manage the UPS Subscription State and Global Subscription State of an AE. + + describes the UPS Subscription State transitions of an AE for a given UPS Instance. Each row in the table defines what should happen in response to a Subscription Action, or a UPS creation event, given the initial state. The table also shows when an initial event message should be sent to the AE describing the "Current UPS State". + + In general, instance specific instructions take precedence over global instructions. The exception is the Unsubscribe Globally instruction, which removes all subscriptions, global and specific. To simply stop globally subscribing to new instances without removing specific subscriptions, use the Suspend Global Subscription message. + + Most actions affect only the UPS Subscription State of a single UPS Instance. However, Global actions potentially affect all existing UPS Instances managed by the SCP and this is indicated in the following table by "All". For example, in the "AE Subscribes Globally with Lock" row, the content of the "Not Subscribed" cell means that in addition to setting the Global Subscription State for the AE to "Global Subscription with Lock", all existing UPS Instances whose UPS Subscription State for the Receiving AE is "Not Subscribed" will each have their UPS Subscription State changed to "Subscribed with Lock" and an event will be sent to the Receiving AE for each Instance. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UPS Subscription State Transition Table
+ + + States (for a specific UPS and AE) + +
+ Events + + + null + + + Not Subscribed + + Subscribed with Lock + + Subscribed w/o Lock +
+ A UPS is Created when the AE Global Subscription State is "No Global Subscription" + + Go to Not Subscribed + + + N/A + + + + N/A + + + + N/A + +
+ A UPS is Created when the AE Global Subscription State is "Global Subscription with Lock" + + Go to Subscribed with Lock; Send initial event + + + N/A + + + + N/A + + + + N/A + +
+ A UPS is Created when the AE Global Subscription State is "Global Subscription w/o Lock" + + Go to Subscribed w/o Lock; Send initial event + + + N/A + + + + N/A + + + + N/A + +
+ AE Subscribes Globally with Lock + + + N/A + + + + AE Global State is now "Global Sub. with Lock"; + + All Go to Subscribed with Lock; All Send initial event + + + AE Global State is now "Global Sub. with Lock"; + + No UPS state change; + + + AE Global State is now "Global Sub. with Lock"; + + No UPS state change; +
+ AE Subscribes Globally w/o Lock + + + N/A + + + + AE Global State is now "Global Sub. w/o Lock"; + All Go to Subscribed w/o Lock; + + + AE Global State is now "Global Sub. w/o Lock"; + + + No UPS state change; + + + + AE Global State is now "Global Sub. w/o Lock"; + + No UPS state change; +
+ AE Subscribes to Specific UPS with Lock + + + N/A + + + Go to Subscribed with Lock; Send initial event + + No UPS state change; Send initial event + + Go to Subscribed with Lock; Send initial event +
+ AE Subscribes to Specific UPS without Lock + + + N/A + + + Go to Subscribed w/o Lock; Send initial event + + Go to Subscribed w/o Lock; Send initial event + + No UPS state change; Send initial event +
+ AE Unsubscribes from Specific UPS + + + N/A + + + No UPS state change + + Go to Not Subscribed + + Go to Not Subscribed +
+ AE Unsubscribes Globally + + + N/A + + + + AE Global State is now "No Global Subscription"; + No UPS state change; + + + AE Global State is now "No Global Subscription"; + + All Go to Not Subscribed; + + + AE Global State is now "No Global Subscription"; + + All Go to Not Subscribed; +
+ AE Suspends Global Subscription + + + N/A + + + + AE Global State is now "No Global Subscription"; + + No UPS state change; + + + AE Global State is now "No Global Subscription"; + + No UPS state change; + + + AE Global State is now "No Global Subscription"; + + No UPS state change; +
+ See Reliable Watchers and Deletion Locks for further discussion of deletion locks. +
+
+ Service Class User Behavior + The SCU subscribing to track the progress and results of the scheduled procedure step may be the system that created the UPS as an SCU of the UPS Push SOP Class, or it may be some other interested observer. + An SCU shall use the N-ACTION primitive to request the SCP to subscribe an AE (usually the requesting SCU) to receive event reports relating to UPS instances managed by the SCP. Since all UPSs are created as instances of the UPS Push SOP Class, the Requested SOP Class UID (0000,0003) in the N-ACTION request shall be the UID of the UPS Push SOP Class. See for further details. + An SCU shall also use the N-ACTION primitive to request the SCP to unsubscribe an AE to stop receiving event reports relating to UPS instances managed by the SCP. Action Information is specified in . The SCU shall always provide the AE-TITLE that is to receive (or stop receiving) the N-EVENT-REPORTs. + To subscribe for events relating to a single specific UPS instance managed by the SCP, the SCU shall use Action Type ID 3 (Subscribe to Receive UPS Event Reports) and provide the SOP Instance UID of the specific UPS instance in the N-ACTION primitive request. The SCU shall indicate a need for the UPS instance to persist after its state has changed to COMPLETED or CANCELED by setting the value of the Deletion Lock to TRUE. Otherwise the SCU shall set the value of the Deletion Lock to FALSE. + To unsubscribe for events relating to a single specific UPS instance managed by the SCP, the SCU shall use Action Type ID 4 (Unsubscribe from Receiving UPS Event Reports) and provide the SOP Instance UID of the specific UPS instance in the N-ACTION primitive request. + To subscribe for events relating to all current and subsequently created UPS instances managed by the SCP, the SCU shall use Action Type ID 3 (Subscribe to Receive UPS Event Reports) and provide the well-known UID 1.2.840.10008.5.1.4.34.5 in the N-ACTION primitive request. The SCU shall indicate a need for UPS instances to persist after their states have changed to COMPLETED or CANCELED by setting the value of the Deletion Lock to TRUE. Otherwise the SCU shall set the value of the Deletion Lock to FALSE. + + This "global subscription" is useful for SCUs that wish to monitor all activities without having to issue regular C-FINDs to identify new UPS instances. + + To subscribe for events relating to a filtered subset of all current and subsequently created UPS instances (Filtered Global Subscription) managed by the SCP, the SCU shall use Action Type ID 3 (Subscribe to Receive UPS Event Reports) and provide both the well-known UID 1.2.840.10008.5.1.4.34.5.1 and a set of Matching Keys and values in the N-ACTION primitive request (see ). The SCU shall indicate a need for UPS instances to persist after their states have changed to COMPLETED or CANCELED by setting the value of the Deletion Lock to TRUE. Otherwise the SCU shall set the value of the Deletion Lock to FALSE. + + The well-known UID for a Filtered Global Subscription is distinct from the Global Subscription well-known UID. + + To unsubscribe for events relating to all current UPS instances managed by the SCP and also stop being subscribed to subsequently created UPS instances, the SCU shall use Action Type ID 4 (Unsubscribe from Receiving UPS Event Reports) and provide the well-known UID 1.2.840.10008.5.1.4.34.5 in the N-ACTION primitive request. + + This "global unsubscription" is useful for SCUs that wish to stop monitoring all activities and release all deletion locks (if any) placed for this subscriber. + + To just stop being subscribed to subsequently created UPS instances, but still continue to receive events for currently subscribed instances managed by the SCP, the SCU shall use Action Type ID 5 (Suspend Global Subscription) and provide the well-known UID 1.2.840.10008.5.1.4.34.5 in the N-ACTION primitive request. + For each UPS instance on which the SCU has placed a deletion lock, either explicitly on the specific instance or implicitly via a global subscription with lock, the SCU shall remove the deletion lock once any needed final state information for the instance has been obtained. The deletion lock may be removed either by unsubscribing or by subscribing with the value of the Deletion Lock set to FALSE. + + The SCP will retain COMPLETED or CANCELED UPS Instances until all deletion locks have been released. Failure by SCUs to release the deletion lock may cause problems for the SCP. SCUs that do not have a significant need for the final state information, or who cannot dependably remove deletion locks should not use deletion locks. + + The successful N-ACTION Response Status Code indicates that the SCP has received the N-ACTION request and the Subscription State for the AE has been successfully modified. + + + + When subscribing to a specific instance, the SCU can also expect to receive an initial N-EVENT-REPORT containing the current state of the UPS instance. When subscribing globally with the Deletion Lock set to TRUE, the SCU can expect to receive initial N-EVENT-REPORTs for every instance currently managed by the SCP. Initial N-EVENT-REPORTs for newly created instances, received as a result of a global subscription, will appear as transitions to the SCHEDULED state. + + + The UPS-RS User-Agent is responsible for opening the N-EVENT-REPORT communication channel (see ). The UPS-RS User-Agent is also responsible for re-establishing the N-EVENT-REPORT communication channel if it is disconnected. This differs from the DIMSE approach where the UPS SCP opens an Association for N-EVENT-REPORT messages as necessary. + + + + A warning N-ACTION Response Status Code of "Deletion Lock not granted", indicates that the AE subscription requested by the SCU was successful, but the deletion lock has not been set. + A failure N-ACTION Response Status Code indicates that the subscription state change requested will not be processed and no subscription states have been changed. The action taken by the SCU upon receiving this status is beyond the scope of this Standard. + At any time after receipt of the N-ACTION-Response, the SCU may release the association on which it sent the N-ACTION-Request. +
+
+ Service Class Provider Behavior + Upon receipt of the N-ACTION request, the SCP shall attempt to update the Global Subscription State and/or UPS Subscription State of the specified AE with respect to the specified SOP Instance UID as described in and then return, via the N-ACTION response primitive, the appropriate N-ACTION Response Status Code. + The SCP may optionally allow an Application Entity to subscribe globally to a filtered set of UPS Instances. In this case, the Application Entity will only be subscribed to existing and future UPS Instances that match the search criteria specified by the Matching Keys of the N-ACTION request (see ). If the SCP does not support Filtered Global Subscription it will return a Failure response with a Code of C307 (see ). + A success status conveys that the Global Subscription State and/or UPS Subscription State for the AE specified in Receiving AE (0074,1234) was successfully modified by the SCP. The AE-TITLE in Receiving AE (0074,1234) may be different than the AE-TITLE used by the SCU for the association negotiation. The SCP shall use the AE-TITLE specified in Receiving AE (0074,1234). This allows systems to subscribe other systems they know would be interested in events for a certain UPS. + For all UPS instances managed by the SCP, the SCP shall send N-EVENT-REPORTS (as described in ) to AEs that have a UPS Subscription State of "Subscribed with Lock" or "Subscribed w/o Lock". If the SCP also supports the HTTP CreateSubscription service as an Origin-Server, the SCP shall also send HTTP SendEventReport messages (see ). + Upon successfully processing a subscription action, the SCP shall send initial UPS State Report N-EVENT-REPORTs, as indicated in , providing the current status of the UPS Instance to the Receiving AE. + The SCP may also refuse both specific and global Subscription requests by returning a failure N-ACTION Response Status Code for "Refused: Not Authorized" if the refusal depends on permissions related to the tasks or the requestor, or "Refused: SCP does not support Event Reports" if the SCP does not support sending the events. The SCP must document in its conformance statement if it might refuse Subscription requests. + The SCP may remove existing Deletion Locks by changing the UPS Subscription State for the AE from "Subscribed with Lock" to "Subscribed w/o Lock" and/or by changing the Global Subscription State for an AE from "Global Subscription with Lock" to "Global Subscription w/o Lock". This is intended to allow the SCP to deal with SCU malfunctions. The SCP must document in its conformance statement if it might remove a Deletion Lock. + The SCP may also refuse the Deletion Lock portion of a specific or global Subscription request. For example, a request to modify the UPS Subscription State for the AE to "Subscribed with Lock" would instead result in a UPS Subscription State of "Subscribed w/o Lock" and a Warning status (see ) returned to the requesting SCU. This is intended to deal with Security and related policy restrictions. The SCP must document in its conformance statement if it might refuse a Deletion Lock. + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. Further requiring or documenting authentication and/or authorization features from the SCU or SCP is beyond the scope of this SOP Class. +
+ Filtered Global Subscription + An SCP that supports Filtered Global Subscription shall create an instance subscription for each UPS Instance that would match a C-FIND request with the Matching Keys provided in the subscription request. + The SCP shall support the same matching logic used for C-FIND (see ). +
+
+
+ Status Codes + The status values that are specific for this DIMSE operation are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + The requested change of subscription state was performed + + 0000 +
+ Warning + + Deletion Lock not granted. + + B301 +
+ Failure + + Specified SOP Instance UID does not exist or is not a UPS Instance managed by this SCP + + C307 +
+ Receiving AE-TITLE is Unknown to this SCP + + C308 +
+ Refused: Specified action not appropriate for specified instance + + C314 +
+ Refused: SCP does not support Event Reports + + C315 +
+
+
+
+ Report a Change in UPS Status (N-EVENT-REPORT) + This operation allows an SCP to notify an SCU of a change in state of a UPS instance or a change in state of the SCP itself. This operation shall be invoked by the SCP through the DIMSE N-EVENT-REPORT Service. +
+ Event Report Information + DICOM AEs that claim conformance to the UPS Event SOP Class as an SCU and/or an SCP shall support the Event Type IDs and Event Report Attributes as specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Report a Change in UPS Status - Event Report Information
+ + Event Type Name + + + + Event Type ID + + + + Attribute Name + + + + Tag + + + + Req. Type SCU/SCP + +
+ UPS State Report + + 1 + + Procedure Step State + + (0074,1000) + + -/1 +
+ Input Readiness State + + (0040,4041) + + -/1 +
+ Reason For Cancellation + + (0074,1238) + + -/3 +
+ Procedure Step Discontinuation Reason Code Sequence + + (0074,100e) + + -/3 +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/1C + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously +
+ >Code Meaning + + (0008,0104) + + -/1 +
+ UPS Cancel Requested + + 2 + + Requesting AE + + (0074,1236) + + -/1 +
+ Reason For Cancellation + + (0074,1238) + + -/1C + Required if provided in the triggering N-ACTION +
+ Procedure Step Discontinuation Reason Code Sequence + + (0074,100e) + + -/1C + Required if provided in the triggering N-ACTION +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/1C + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously +
+ >Code Meaning + + (0008,0104) + + -/1 +
+ Contact URI + + (0074,100a) + + -/1C + Required if provided in the triggering N-ACTION +
+ Contact Display Name + + (0074,100c) + + -/1C + Required if provided in the triggering N-ACTION +
+ UPS Progress Report + + 3 + + Progress Information Sequence + + (0074,1002) + + -/1 +
+ >Procedure Step Progress + + (0074,1004) + + -/3 +
+ >Procedure Step Progress Description + + (0074,1006) + + -/3 +
+ >Procedure Step Communications URI Sequence + + (0074,1008) + + -/3 +
+ >>Contact URI + + (0074,100a) + + -/1 +
+ >>Contact Display Name + + (0074,100c) + + -/3 +
+ SCP Status Change + + 4 + + SCP Status + + (0074,1242) + + -/1 +
+ Subscription List Status + + (0074,1244) + + -/1 +
+ Unified Procedure Step List Status + + (0074,1246) + + -/1 +
+ UPS Assigned + + 5 + + Scheduled Station Name Code Sequence + + (0040,4025) + + -/1C + Required if populated in the UPS Instance +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/1C + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously +
+ >Code Meaning + + (0008,0104) + + -/1 +
+ Human Performer Code Sequence + + (0040,4009) + + -/1C + Required if populated in the UPS Instance +
+ >Code Value + + (0008,0100) + + -/1 +
+ >Coding Scheme Designator + + (0008,0102) + + -/1 +
+ >Coding Scheme Version + + (0008,0103) + + -/1C + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously +
+ >Code Meaning + + (0008,0104) + + -/1 +
+ Human Performer's Organization + + (0040,4036) + + -/1C + Required if populated in the UPS Instance +
+ + The meanings of the Progress Information Attribute values in the context of a specific task are undefined, and the values may be obsolete when the UPS has moved to the COMPLETED or CANCELED state. + +
+
+ Service Class User Behavior + The SCU shall return, via the N-EVENT-REPORT response primitive, the N-EVENT-REPORT Response Status Code applicable to the associated request. See for general response status codes. + The SCU shall accept all Attributes included in any notification. No requirements are placed on what the SCU will do as a result of receiving this information. + + An SCU may receive N-EVENT-REPORTs with an Event Type ID of 1 (UPS State Report) either due to a state change to the UPS, or in response to initial subscription to the UPS (possibly when the UPS is initially created). See . + + If an SCU performing a UPS receives an N-EVENT-REPORT for that instance with an Event Type ID of 2 (UPS Cancel Requested), then this SCU may, at its own discretion, choose to cancel the UPS as described in . + + + + A UPS Cancel Requested notification includes the AE of the Requesting SCU, which could be useful to the performing SCU in deciding the significance/authority of the Cancel Request. + + + The Reason For Cancellation, a proposed Procedure Step Discontinuation Reason Code Sequence, a Contact Display Name and a Contact URI of someone responsible for the Cancel Request may also be provided in the notification. Some performing SCUs might find this information useful in deciding to cancel the UPS or might provide the information to an operator so they can make contact for the purpose of clarifying or confirming the Cancel Request. If the performing SCU chooses to Cancel the UPS, it may at its own discretion set the Procedure Step Discontinuation Reason Code Sequence in the UPS instance based on the corresponding values provided. + + + + If an SCU receives an N-EVENT-REPORT for an instance with an Event Type ID of 5 (UPS Assigned) and the device or human specified in the notification correspond to the SCU, then the UPS has been assigned to that SCU. + An SCU that wishes to start/stop receiving N-EVENT-REPORTs about UPS instances may subscribe/unsubscribe as described in . + If an SCU receives an N-EVENT-REPORT with an Event Type ID of 4 (SCP Status Change), it is not required to act on that information, however the SCU may want to consider actions such as: re-subscribing if the subscription list has been Cold Started, verifying (and recreating if necessary) scheduled UPSs if the UPS list has been Cold Started, etc. + + An SCU may receive SCP State Change Events from any SCP with which it is currently subscribed either globally or for any specific UPS. + +
+
+ Service Class Provider Behavior + The SCP shall specify in the N-EVENT-REPORT Request Primitive the Event Type ID and the UID of the UPS Instance with which the event is associated. Since all UPSs are created as instances of the UPS Push SOP Class, the Affected SOP Class UID (0000,0002) in the N-EVENT-REPORT request shall be the UID of the UPS Push SOP Class. See for further details. The SCP shall additionally include Attributes related to the event as defined in . + Each time the SCP completes a Subscribe to Receive UPS Event Reports Action (see ) for a specific UPS instance, the SCP shall send to the Receiving AE a UPS State Report Event and provide the current value of the Procedure Step State (0074,1000) and Input Readiness State (0040,4041) Attributes for the UPS instance. + Each time the SCP completes a Subscribe to Receive UPS Event Reports Action (see ) for the well-known UID 1.2.840.10008.5.1.4.34.5 with the value of the Deletion Lock set to TRUE (i.e., a Global Subscription with Lock), the SCP shall send to the Receiving AE a UPS State Report Event for every UPS Instance managed by the SCP and provide the current value of the Procedure Step State (0074,1000) and Input Readiness State (0040,4041) Attributes. + Each time the SCP creates a new UPS instance, the SCP shall send a UPS State Report Event, indicating a change of status to SCHEDULED and the initial value of and Input Readiness State (0040,4041), to all AEs with a Global Subscription State of "Global Subscription with Lock" or "Global Subscription w/o Lock". (see ) + Each time the SCP creates a new UPS instance and either the Scheduled Station Name Code Sequence (0040,4025) or the Scheduled Human Performers Sequence (0040,4034) is populated, the SCP shall also send a UPS Assigned Event, with the current contents of the Scheduled Station Name Code Sequence (0040,4025) and the Scheduled Human Performers Sequence (0040,4034), to all AEs with a Global Subscription State of "Global Subscription with Lock" or "Global Subscription w/o Lock". + In the following text "Subscribed SCUs" means all AEs where the UPS Subscription State of the UPS Instance in question is "Subscribed with Lock" or "Subscribed w/o Lock" (see ). If the SCP also supports the HTTP CreateSubscription service as an Origin-Server, "Subscribed SCUs" also includes all CreateSubscription User-Agents where the UPS Subscription State of the UPS Instance in question is "Subscribed with Lock" or "Subscribed w/o Lock" (see ). + Each time the SCP changes the Procedure Step State (0074,1000) Attribute for a UPS instance, the SCP shall send a UPS State Report Event to subscribed SCUs. + Each time the SCP changes the Input Readiness State (0040,4041) Attribute for a UPS instance, the SCP shall send a UPS State Report Event to subscribed SCUs. + Each time the SCP receives an N-ACTION with an Action Type ID of 2 (Request UPS Cancel), the SCP shall send a UPS Cancel Requested Event to subscribed SCUs. The SCP shall include the AE Title of the triggering N-ACTION SCU in the Requesting AE Attribute. The SCP shall include the Reason For Cancellation, Contact Display Name and Contact URI Attributes if they were provided in the triggering N-ACTION. + Each time the SCP updates the Scheduled Station Name Code Sequence (0040,4025) or the Scheduled Human Performers Sequence (0040,4034) for a UPS instance, the SCP shall send a UPS Assigned Event, with the current contents of the Scheduled Station Name Code Sequence (0040,4025) and the Scheduled Human Performers Sequence (0040,4034), to subscribed SCUs. + Each time the SCP updates the Procedure Step Progress (0074,1004), the Procedure Step Progress Description (0074,1006), or the contents of the Procedure Step Communications URI Sequence (0074,1008) for a UPS instance, the SCP shall send a UPS Progress Event, with the current contents of the Progress Information Sequence (0074,1002), to subscribed SCUs. + Each time the SCP is restarted, the SCP shall send an SCP Status Change Event. The SCP, if it knows it is going down, may send an additional SCP Status Change Event before it is shut down. Since the subscription lists may be incomplete or missing in the event of a restart, the SCP shall maintain a fallback list of AEs (for example as a configuration file, or from an LDAP server). The SCP shall send the SCP Status Change Events to: + + + all AEs on the fallback list and, + + + all AEs with a Global Subscription State of "Global Subscription with Lock" or "Global Subscription w/o Lock" and, + + + all AEs with a UPS Subscription State of "Subscribed with Lock" or "Subscribed w/o Lock" for any UPS Instance managed by the SCP + + + + The SCP may choose to not send duplicate messages to an AE. + + The value of SCP Status (0074,1242) shall be RESTARTED if the SCP is sending this message due to being restarted and GOING DOWN if the SCP will be shut down soon. + + SCPs that report they are GOING DOWN might stop accepting new interactions from SCUs until after they have restarted. + + When SCP Status (0074,1242) is RESTARTED, the value of Subscription List Status (0074,1244) shall be WARM START if the SCP preserved the Subscription List to the best of its knowledge, and COLD STARTED if the SCP has not preserved the Subscription List. + When SCP Status (0074,1242) is RESTARTED, the value of Unified Procedure Step List Status (0074,1246) shall be WARM START if the SCP preserved the UPS List to the best of its knowledge, and COLD START if the SCP has not preserved the UPS List. + If the SCP is unable to successfully complete an N-EVENT-REPORT to any given SCU, the SCP has no obligation to queue or retry, and it should not imply any effect on the subscription list or deletion locks. +
+
+ Status Codes + No Service Class specific status values are defined for the N-EVENT-REPORT Service. See for general response status codes. +
+
+
+ Create a Unified Procedure Step (N-CREATE) + This operation allows an SCU to instruct an SCP to create a Unified Procedure Step. This operation shall be invoked by the SCU through the DIMSE N-CREATE Service. +
+ Unified Procedure Step Attribute Specification + An Application Entity that claims conformance to the UPS Push SOP Class as an SCU shall provide all Required Attributes as specified in . Additional Attributes defined by the UPS IOD may be provided as well. + An Application Entity that claims conformance to the UPS Push SOP Class as an SCP shall support all required Attributes as specified in . Additional Attributes defined by the UPS IOD may be supported as well. +
+ UPS Final State Requirements + COMPLETED and CANCELED are Final States for a UPS instance. The Attributes and values of the UPS instance must meet certain requirements before it may be placed in either of the Final States. + + A UPS instance is in the SCHEDULED state when created. See for rules governing state transitions. + + Attributes shall be valued as indicated by the Final State Codes in the Final State Column of before the Procedure Step State (0074,1000) may be set to COMPLETED or CANCELED (i.e., Final State). + Performing systems are encouraged to ensure that the values for all Attributes reasonably reflect what was done and the Final State of the UPS. This may include blanking Attributes that are permitted to be empty and for which no reasonable value can be determined. The UPS contents should make it clear whether the step was completed, what work was done, what results were produced and whether the results are usable. See for a discussion of methods to convey things like partial completion. + + The SCU may choose not to distribute, or otherwise make available, some or all instances created during the procedure step and referenced in the Output Information Sequence (0040,4033). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Final State Codes
+ + Final State Code + + + + Meaning + +
+ R + + The UPS State shall not be set to COMPLETED or CANCELED if this Attribute does not have a value. +
+ RC + + The UPS State shall not be set to COMPLETED or CANCELED if the condition is met and this Attribute does not have a value. +
+ P + + The UPS State shall not be set to COMPLETED if this Attribute does not have a value, but may be set to CANCELED. +
+ X + + The UPS State shall not be set to CANCELED if this Attribute does not have a value, but may be set to COMPLETED. +
+ O + + The UPS State may be set to either COMPLETED or CANCELED if this Attribute does not have a value. +
+
+
+ UPS Macros + To reduce the size and complexity of , a macro notation is used. + For example, in , a table entry specifying "Include " should be interpreted as including the following table of text as a substitution. The nesting level for the sequence inclusion is indicated by the nesting level on the reference to the macro. Where the matching key type requirement is "*" it should be replaced with the matching key type requirement of the sequence Attribute that incorporates this macro. + For code sequences that have requirements for N-CREATE, N-SET, N-GET, or C-FIND behavior that differ from the Macro, the code sequence contents are explicitly listed in the Table rather than specifying inclusion of the Macro. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UPS Code Sequence Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Code Value + + (0008,0100) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + Code Value shall be retrieved with Single Value Matching. +
+ Coding Scheme Designator + + (0008,0102) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + Coding Scheme Designator shall be retrieved with Single Value Matching. +
+ Coding Scheme Version + + (0008,0103) + + 1C/1C + + 1C/1C + + + -/1 + + - + + 1C + + Required if the value of Coding Scheme Designator (0008,0102) is not sufficient to identify the Code Value (0008,0100) unambiguously. +
+ Code Meaning + + (0008,0104) + + 1/1 + + 1/1 + + + -/1 + + - + + 1 + + Code Meaning shall not be used as Matching Key. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UPS Content Item Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Value Type + + (0040,A040) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + The type of the value encoded in this name-value Item. + + Enumerated Values: + + DATETIME + + + + + + DATE + + + + + + TIME + + + + + + PNAME + + + + + + UIDREF + + + + + + TEXT + + + + + + CODE + + + + + + NUMERIC + + + + + +
+ Concept Name Code Sequence + + (0040,A043) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + Coded concept name of this name-value Item. +
+ + >Include + + + + + No Baseline CID is defined. + +
+ DateTime + + (0040,A120) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Datetime value for this name-value Item. + Required if Value Type (0040,A040) is DATETIME. +
+ Date + + (0040,A121) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Date value for this name-value Item. + Required if Value Type (0040,A040) is DATE. +
+ Time + + (0040,A122) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Time value for this name-value Item. + Required if Value Type (0040,A040) is TIME. +
+ Person Name + + (0040,A123) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Person name value for this name-value Item. + Required if Value Type (0040,A040) is PNAME. +
+ UID + + (0040,A124) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + UID value for this name-value Item. + Required if Value Type (0040,A040) is UIDREF. +
+ Text Value + + (0040,A160) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Text value for this name-value Item. + Required if Value Type (0040,A040) is TEXT. +
+ Concept Code Sequence + + (0040,A168) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Coded concept value of this name-value Item. + Required if Value Type (0040,A040) is CODE. +
+ + >Include + + + + + No Baseline CID is defined. + +
+ Numeric Value + + (0040,A30A) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Numeric value for this name-value Item. + Required if Value Type (0040,A040) is NUMERIC. +
+ Measurement Units Code Sequence + + (0040,08EA) + + 1C/1C + + 1/1 + + + -/1 + + * + + 1C + + Units of measurement for a numeric value in this name-value Item. + Required if Value Type (0040,A040) is NUMERIC. +
+ + >Include + + + + + Baseline + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Referenced Instances and Access Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Type of Instances + + (0040,E020) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ Study Instance UID + + (0020,000D) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if Type of Instances (0040,E020) is DICOM +
+ Series Instance UID + + (0020,000E) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if Type of Instances (0040,E020) is DICOM +
+ Referenced SOP Sequence + + (0008,1199) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ >Referenced SOP Class UID + + (0008,1150) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ >Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ >HL7 Instance Identifier + + (0040,E001) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if Type of Instances (0040,E020) is CDA. +
+ >Referenced Frame Number + + (0008,1160) + + 1C/1 + + 1C/1 + + + -/2 + + O + + 1C + + Required if the Referenced SOP Instance is a multi-frame image and the reference does not apply to all frames, and Referenced Segment Number (0062,000B) is not present. +
+ >Referenced Segment Number + + (0062,000B) + + 1C/1 + + 1C/1 + + + -/2 + + O + + 1C + + Required if the Referenced SOP Instance is a Segmentation and the reference does not apply to all segments and Referenced Frame Number (0008,1160) is not present. +
+ DICOM Retrieval Sequence + + (0040,E021) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Media Retrieval Sequence (0040,E022), WADO Retrieval Sequence (0040,E023), WADO-RS Retrieval Sequence (0040,E025) and XDS Retrieval Sequence (0040,E024) are not present. May be present otherwise. +
+ >Retrieve AE Title + + (0008,0054) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ DICOM Media Retrieval Sequence + + (0040,E022) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Retrieval Sequence (0040,E021), WADO Retrieval Sequence (0040,E023), WADO-RS Retrieval Sequence (0040,E025) and XDS Retrieval Sequence (0040,E024) are not present. May be present otherwise. +
+ >Storage Media File-Set ID + + (0088,0130) + + 2/2 + + 2/2 + + + -/2 + + O + + 2 + +
+ >Storage Media File-Set UID + + (0088,0140) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ WADO Retrieval Sequence + + (0040,E023) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Retrieval Sequence (0040,E021), DICOM Media Retrieval Sequence (0040,E022), WADO-RS Retrieval Sequence (0040,E025) and XDS Retrieval Sequence (0040,E024) are not present. May be present otherwise. +
+ >Retrieve URI + + (0040,E010) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + +
+ XDS Retrieval Sequence + + (0040,E024) + + 1C + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Retrieval Sequence (0040,E021), DICOM Media Retrieval Sequence (0040,E022), WADO-RS Retrieval Sequence (0040,E025) and WADO Retrieval Sequence (0040,E023) are not present. May be present otherwise. +
+ >Repository Unique ID + + (0040,E030) + + 1 + + 1/1 + + + -/1 + + O + + 1 + +
+ >Home Community ID + + (0040,E031) + + 3/2 + + 3/2 + + + 3/2 + + O + + 2 + +
+ WADO-RS Retrieval Sequence + + (0040,E025) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Retrieval Sequence (0040,E021), DICOM Media Retrieval Sequence (0040,E022), WADO Retrieval Sequence (0040,E023), and XDS Retrieval Sequence (0040,E024) are not present. May be present otherwise. +
+ >Retrieve URL + + (0008,1190) + + 1/1 + + 1/1 + + + -/1 + + O + + 1 + + URL specifying the location of the referenced instance(s). +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
HL7V2 Hierarchic Designator Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Local Namespace Entity ID + + (0040,0031) + + 1C/1 + + Not Allowed + + + -/1 + + * + + 1C + + Creation required if Universal Entity ID (0040,0032) is not present; may be present otherwise. + Return Key required if set. +
+ Universal Entity ID + + (0040,0032) + + 1C/1 + + Not Allowed + + + -/1 + + * + + 1C + + Creation required if Local Namespace Entity ID (0040,0031) is not present; may be present otherwise. + Return Key required if set. +
+ Universal Entity ID Type + + (0040,0033) + + 1C/1 + + Not Allowed + + + -/1 + + * + + 1C + + Creation required if Universal Entity ID (0040,0032) is present. + Return Key required if set. +
+ Local Namespace Entity ID + + (0040,0031) + + 1C/1 + + Not Allowed + + + -/1 + + * + + 1C + + Creation required if Universal Entity ID (0040,0032) is not present; may be present otherwise. + Return Key required if set. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Issuer of Patient ID Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Issuer of Patient ID + + (0010,0021) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + +
+ Issuer of Patient ID Qualifiers Sequence + + (0010,0024) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + +
+ >Universal Entity ID + + (0040,0032) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + +
+ >Universal Entity ID Type + + (0040,0033) + + 1C/1 + + Not allowed + + O + + 3/2 + + O + + 1C + + Required if Universal Entity ID (0040,0032) is present in this item with a value. +
+ >Identifier Type Code + + (0040,0035) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + +
+ >Assigning Facility Sequence + + (0040,0036) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + + The Attributes of the Assigning Facility Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + + +
+ >Assigning Jurisdiction Code Sequence + + (0040,0039) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + + The Attributes of the Assigning Jurisdiction Code Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + + + + Baseline for country codes. + +
+ >Assigning Agency or Department Code Sequence + + (0040,003A) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + + The Attributes of the Assigning Agency or Department Code Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + + + + No Baseline CID. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SOP Instance Reference Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Referenced SOP Class UID + + (0008,1150) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + +
+ Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Storage Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Referenced SOP Class UID + + (0008,1150) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if the storage request only applies to a specific SOP Class. +
+ DICOM Storage Sequence + + (0040,4071) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if STOW-RS Storage Sequence (0040,4072) and XDS Storage Sequence (0040,4074) are not present. May be present otherwise. +
+ >Destination AE + + (0040,4071) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + +
+ STOW-RS Storage Sequence + + (0040,4072) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Storage Sequence (0040,4071) and XDS Storage Sequence (0040,4074) are not present. May be present otherwise. +
+ >Storage URL + + (0040,4073) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + +
+ XDS Storage Sequence + + (0040,4074) + + 1C/1 + + 1C/1 + + + -/1 + + O + + 1C + + Required if DICOM Storage Sequence (0040,4071) and STOW-RS Storage Sequence (0040,4072) are not present. May be present otherwise. +
+ >Repository Unique ID + + (0040,E030) + + 1/1 + + 1/1 + + + -/1 + + * + + 1 + + +
+ >Home Community ID + + (0040,E031) + + 3/2 + + 3/2 + + + 3/2 + + * + + 2 + + +
+
+
+ UPS Attribute Service Requirements + This table combines the Attribute requirements for multiple DIMSE services (N-CREATE, N-SET, N-GET, C-FIND) to facilitate consistency between the requirements. + See PS3.4 for the meaning of the requirement codes used in the N-CREATE, N-SET, N-GET and Return Key columns in the following table. + See for the meaning of the requirement codes used in the Match Key column in the following table. + See for the meaning of the requirement codes used in the Final State column of the following table. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UPS SOP Class N-CREATE/N-SET/N-GET/C-FIND Attributes
+ + Attribute Name + + + + Tag + + + + Req. Type N-CREATE (SCU/SCP) + + + + Req. Type N-SET (SCU/SCP) + + + + Final State + + + + Req. Type N-GET (SCU/SCP) + + + + Match Key Type + + + + Return Key Type + + + + Remark/Matching Type + +
+ Transaction UID + + (0008,1195) + + 2/2 + Shall be empty + + (see ) + + O + + Not allowed + + - + + - + + Cannot be queried. +
+ + SOP Common Module + +
+ Specific Character Set + + (0008,0005) + + 1C/1C + + 1C/1C + + RC + + 3/1 + + - + + 1C + + Required if extended or replacement character set is used +
+ SOP Class UID + + (0008,0016) + + See + + + Not allowed + + R + + Not allowed + + O + + 1 + + Uniquely identifies the SOP Class of the Unified Procedure Step. + See for further explanation. +
+ SOP Instance UID + + (0008,0018) + + Not allowed. + SOP Instance is conveyed in the Affected SOP Instance UID (0000,1000) + + Not allowed. + SOP Instance is conveyed in the Requested SOP Instance UID (0000,1001) + + R + + Not allowed. + SOP Instance is conveyed in the Requested SOP Instance UID (0000,1001) + + U + + 1 + + Uniquely identifies the SOP Instance of the UPS. + SOP Instance UID shall be retrieved with Single Value Matching. +
+ + All other Attributes of the + + + + + 3/3 + + 3/3 + + O + + 3/3 + + - + + - + +
+ + Unified Procedure Step Scheduled Procedure Information Module + +
+ Scheduled Procedure Step Priority + + (0074,1200) + + 1/1 + + 3/1 + + R + + 3/1 + + R + + 1 + + Scheduled Procedure Step Priority shall be retrieved with Single Value Matching. +
+ Scheduled Procedure Step Modification Date and Time + + (0040,4010) + + 2/1 + SCP shall use time of CREATE rather than any value provided + + -/1 + SCP will use time of SET + + R + + 3/1 + + O + + 3 + + Scheduled Procedure Step Modification Date and Time shall be retrieved with Single Value Matching or Range Matching. +
+ Procedure Step Label + + (0074,1204) + + 1/1 + + 3/1 + + O + + 3/1 + + R + + 1 + +
+ Worklist Label + + (0074,1202) + + 2/1 + If a value is not provided by the SCU, the SCP shall fill in the Worklist Label, e.g., using a default value or by assigning the UPS instance to a logical worklist. + + 3/1 + + O + + 3/1 + + R + + 1 + +
+ Scheduled Processing Parameters Sequence + + (0074,1210) + + 2/2 + + 3/2 + + O + + 3/2 + + - + + 2 + +
+ + >Include + + +
+ Scheduled Station Name Code Sequence + + (0040,4025) + + 2/2 + + 3/2 + + O + + 3/2 + + R + + 2 + + The Attributes of the Scheduled Station Name Code Sequence shall only be retrieved with Sequence Matching. + + In Push Scenario, the SCP-Performer has to create empty but could self fill later. + +
+ + >Include + + +
+ Scheduled Station Class Code Sequence + + (0040,4026) + + 2/2 + + 3/2 + + O + + 3/2 + + R + + 2 + + The Attributes of the Scheduled Station Class Code Sequence shall only be retrieved with Sequence Matching. +
+ + >Include + + +
+ Scheduled Station Geographic Location Code Sequence + + (0040,4027) + + 2/2 + + 3/2 + + O + + 3/2 + + R + + 2 + + The Attributes of the Scheduled Station Geographic Location Code Sequence shall only be retrieved with Sequence Matching. +
+ + >Include + + +
+ Scheduled Human Performers Sequence + + (0040,4034) + + 2C/2C + + 3/2 + + O + + 3/2 + + R + + 2 + + The Attributes of the Scheduled Human Performers Sequence shall only be retrieved with Sequence Matching. + Required if a Human Performer is specified. +
+ >Human Performer Code Sequence + + (0040,4009) + + 1/1 + + 1/1 + + O + + -/1 + + R + + 1 + + The Attributes of the Scheduled Human Performers Code Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + +
+ >Human Performer's Name + + (0040,4037) + + 1/1 + + 1/1 + + O + + -/1 + + O + + 3 + +
+ >Human Performer's Organization + + (0040,4036) + + 1/1 + + 1/1 + + O + + -/1 + + O + + 3 + +
+ Scheduled Procedure Step Start Date and Time + + (0040,4005) + + 1/1 + + 3/1 + + R + + 3/1 + + R + + 1 + + Scheduled Procedure Step Start Date and Time shall be retrieved with Single Value Matching or Range Matching. +
+ Expected Completion Date and Time + + (0040,4011) + + 3/1 + + 3/1 + + O + + 3/1 + + R + + 3 + + Expected Completion Date and Time shall be retrieved with Single Value Matching or Range Matching. +
+ Scheduled Workitem Code Sequence + + (0040,4018) + + 2/2 + + 3/1 + + O + + 3/1 + + R + + 2 + + The Attributes of the Scheduled Workitem Code Sequence shall only be retrieved with Sequence Matching. +
+ + >Include + + +
+ Comments on the Scheduled Procedure Step + + (0040,0400) + + 2/2 + + 3/1 + + O + + 3/1 + + O + + 3 + +
+ Input Readiness State + + (0040,4041) + + 1/1 + + 3/1 + + R + + 3/1 + + R + + 1 + + Input Readiness State shall be retrieved with Single Value Matching. +
+ Input Information Sequence + + (0040,4021) + + 2/2 + + 3/2 + + O + + 3/2 + + O + + 2 + + The Attributes of the Input Information Sequence shall only be retrieved with Sequence Matching. +
+ + >Include + + +
+ Study Instance UID + + (0020,000D) + + 1C/2 + + 3/2 + + O + + 3/2 + + O + + 2 + + Required if the Workitem is expected to result in the creation of any DICOM Composite Instances whose IOD contains the Study IE. + There may be situations where the performer does not use the Study Instance UID suggested by the Scheduler. +
+ Output Destination Sequence + + (0040,4070) + + 3/3 + + 3/3 + + O + + 3/3 + + O + + 3 + + The Attributes of the Output Destination Sequence shall only be retrieved with Sequence Matching. +
+ + >Include + + +
+ + All other Attributes of the + + + + + 3/3 + + 3/3 + + O + + 3/3 + + - + + - + +
+ + Unified Procedure Step Relationship Module + +
+ Patient's Name + + (0010,0010) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + +
+ Patient ID + + (0010,0020) + + 1C/2 + + Not allowed + + O + + 3/2 + + R + + 2 + + Required if the subject of the workitem requires identification or if the workitem is expected to result in the creation of objects that identify the subject. + See + +
+ + Include + + +
+ Other Patient IDs Sequence + + (0010,1002) + + 2/2 + + 3/3 + + O + + 3/2 + + O + + 2 + +
+ >Patient ID + + (0010,0020) + + 1/1 + + 1/1 + + O + + -/1 + + O + + 1 + +
+ + >Include + + +
+ Patient's Birth Date + + (0010,0030) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + +
+ Patient's Sex + + (0010,0040) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + +
+ Referenced Patient Photo Sequence + + (0010,1100) + + 3/3 + + 3/3 + + O + + 3/3 + + - + + 3 + +
+ + >Include + + +
+ Admission ID + + (0038,0010) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + +
+ Issuer of Admission ID Sequence + + (0038,0014) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + +
+ + >Include + + +
+ Admitting Diagnoses Description + + (0008,1080) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + +
+ Admitting Diagnoses Code Sequence + + (0008,1084) + + 2/2 + + Not allowed + + O + + 3/2 + + O + + 2 + + The Attributes of the Admitting Diagnoses Code Sequence shall only be retrieved with Sequence Matching. +
+ + >Include . + +
+ Referenced Request Sequence + + (0040,A370) + + 2/2 + + Not allowed + + O + + 3/2 + + R + + 2 + + Could be "changed" while SCHEDULED by canceling and re-creating with the "correct" values. +
+ >Study Instance UID + + (0020,000D) + + 1/1 + + Not allowed + + O + + -/1 + + O + + 1 + +
+ >Accession Number + + (0008,0050) + + 2/2 + + Not allowed + + O + + -/2 + + R + + 2 + +
+ >Issuer of Accession Number Sequence + + (0008,0051) + + 2/2 + + Not allowed + + O + + -/2 + + R + + 2 + + The Issuer of Accession Number Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + +
+ >Placer Order Number/Imaging Service Request + + (0040,2016) + + 3/1 + + Not allowed + + O + + -/1 + + O + + 1C + + Required if set. +
+ >Order Placer Identifier Sequence + + (0040,0026) + + 2/2 + + Not allowed + + O + + -/2 + + O + + 2 + + The Order Placer Identifier Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + +
+ >Filler Order Number/Imaging Service Request + + (0040,2017) + + 3/1 + + Not allowed + + O + + -/1 + + O + + 1C + + Required if set. +
+ >Order Filler Identifier Sequence + + (0040,0027) + + 2/2 + + Not allowed + + O + + -/2 + + O + + 2 + + The Order Filler Identifier Sequence shall only be retrieved with Sequence Matching. +
+ + >>Include + + +
+ >Requested Procedure ID + + (0040,1001) + + 2/2 + + Not allowed + + O + + -/2 + + R + + 2 + +
+ >Requested Procedure Description + + (0032,1060) + + 2/2 + + Not allowed + + O + + -/2 + + O + + 2 + +
+ >Requested Procedure Code Sequence + + (0032,1064) + + 2/2 + + Not allowed + + O + + -/2 + + O + + 2 + +
+ + >>Include + + +
+ >Reason for the Requested Procedure + + (0040,1002) + + 3/3 + + 3/3 + + O + + -/3 + + - + + - + +
+ > Reason for Requested Procedure Code Sequence + + (0040,100A) + + 3/3 + + 3/3 + + O + + -/3 + + - + + - + +
+ + >>Include + + +
+ >Requested Procedure Comments + + (0040,1400) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 1C + + Required if set. +
+ >Confidentiality Code + + (0040,1008) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ >Names of Intended Recipients of Results + + (0040,1010) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ >Imaging Service Request Comments + + (0040,2400) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ >Requesting Physician + + (0032,1032) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ >Requesting Service + + (0032,1033) + + 3/1 + + 3/1 + + O + + -/3 + + R + + 3 + +
+ >Issue Date of Imaging Service Request + + (0040,2004) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ >Issue Time of Imaging Service Request + + (0040,2005) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ >Referring Physician's Name + + (0008,0090) + + 3/3 + + 3/3 + + O + + -/3 + + O + + 3 + +
+ Replaced Procedure Step Sequence + + (0074,1224) + + 1C/1C + + Not allowed + + O + + 3/2 + + R + + 3 + + Required if the UPS replaces another Procedure Step. +
+ + >Include + + +
+ + Patient Medical Module + +
+ Medical Alerts + + (0010,2000) + + 3/2 + + 3/2 + + O + + 3/2 + + O + + 2C + + Required if present. +
+ Pregnancy Status + + (0010,21C0) + + 3/2 + + 3/2 + + O + + 3/2 + + O + + 2C + + Required if present. +
+ Special Needs + + (0038,0050) + + 3/2 + + 3/2 + + O + + 3/2 + + O + + 2C + + Required if present. +
+ + All other Attributes of the + + + + + 3/3 + + 3/3 + + O + + 3/3 + + O + + 3 + +
+ + Unified Procedure Step Progress Information Module + +
+ Procedure Step State + + (0074,1000) + + 1/1 + Shall be created with a value of "SCHEDULED" + + Not Allowed. + Use N-ACTION + + R + + 3/1 + + R + + 1 + + Procedure Step State shall be retrieved with Single Value Matching +
+ Progress Information Sequence + + (0074,1002) + + 2/2 + Shall be empty + + 3/2 + + X + + 3/2 + + + 2 + +
+ >Procedure Step Progress + + (0074,1004) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Procedure Step Progress Description + + (0074,1006) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Procedure Step Communications URI Sequence + + (0074,1008) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >>Contact URI + + (0074,100a) + + Not Allowed + + 1/1 + + O + + -/1 + + - + + - + +
+ >>Contact Display Name + + (0074,100c) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Procedure Step Cancellation DateTime + + (0040,4052) + + Not Allowed + + 3/1 + + X + + -/1 + + - + + - + + If changing the UPS State (0074,1000) to CANCELED and this Attribute has no value, the SCP shall fill it with the current datetime. +
+ >Reason For Cancellation + + (0074,1238) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Procedure Step Discontinuation Reason Code Sequence + + (0074,100e) + + Not Allowed + + 3/1 + + X + + -/1 + + + +
+ + >>Include + + +
+ + Unified Procedure Step Performed Procedure Information Module + +
+ Unified Procedure Step Performed Procedure Sequence + + (0074,1216) + + 2/2 + Shall be created empty + + 3/2 + + P + + 3/2 + + - + + - + + See . +
+ >Actual Human Performers Sequence + + (0040,4035) + + Not Allowed + + 3/1 + + RC + + -/1 + + O + + 1C + + Shall be provided if known. + Return Key required if set. + The Attributes of the Actual Human Performers Sequence shall only be retrieved with Sequence Matching. +
+ >>Human Performer Code Sequence + + (0040,4009) + + Not Allowed + + 3/1 + + RC + + -/1 + + - + + - + + Shall be provided if known. +
+ + >>>Include + + +
+ >>Human Performer's Name + + (0040,4037) + + Not Allowed + + 3/1 + + RC + + -/1 + + - + + - + + Shall be provided if known +
+ >>Human Performer's Organization + + (0040,4036) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Performed Station Name Code Sequence + + (0040,4028) + + Not Allowed + + 3/2 + + P + + -/2 + + O + + 3 + +
+ + >>Include + + +
+ >Performed Station Class Code Sequence + + (0040,4029) + + Not Allowed + + 3/2 + + O + + -/2 + + - + + - + +
+ + >>Include + + +
+ >Performed Station Geographic Location Code Sequence + + (0040,4030) + + Not Allowed + + 3/2 + + O + + -/2 + + - + + - + +
+ + >>Include + + +
+ >Performed Procedure Step Start DateTime + + (0040,4050) + + Not Allowed + + 3/1 + + P + + -/1 + + - + + - + +
+ >Performed Procedure Step Description + + (0040,0254) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Comments on the Performed Procedure Step + + (0040,0280) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ >Performed Workitem Code Sequence + + (0040,4019) + + Not Allowed + + 3/1 + + P + + -/1 + + - + + - + +
+ + >>Include + + +
+ >Performed Processing Parameters Sequence + + (0074,1212) + + Not Allowed + + 3/1 + + O + + -/1 + + - + + - + +
+ + >>Include + + +
+ >Performed Procedure Step End DateTime + + (0040,4051) + + Not Allowed + + 3/1 + + P + + -/1 + + O + + 1C + + Required if set. +
+ >Output Information Sequence + + (0040,4033) + + Not Allowed + + 2/2 + + P + + -/2 + + - + + - + + If there are no relevant output objects, then this sequence may have no items. +
+ + >Include + + +
+
+ UPS SOP Class UID + The SOP Class UID shall be set to 1.2.840.10008.5.1.4.34.6.1 by SCP +
+
+ Unified Procedure Step Performed Procedure Sequence + The Attributes of the UPS Performed Procedure Sequence shall only be retrieved with Sequence Matching. + + Since this Attribute may be created empty and has a Final State requirement of X, a UPS in the SCHEDULED state may be canceled with two N-ACTIONS (IN PROGRESS then CANCELED) and no N-SETs. + +
+
+
+
+ Service Class User Behavior + An SCU uses N-CREATE to request the SCP schedule a new UPS. + The SCU shall specify in the N-CREATE request primitive the UPS Push SOP Class UID and the SOP Instance UID for the UPS that is to be created and for which Attribute Values are to be provided. See for further discussion of UPS SOP Class UIDs. + The SCU shall provide Attribute values in the N-CREATE request primitive for all required UPS Attributes as specified in . Additionally, values may be provided for optional Attributes as specified in . + The SCU shall specify a value of "SCHEDULED" for the Attribute Procedure Step State (0074,1000) in the N-CREATE request primitive. +
+
+ Service Class Provider Behavior + The SCP shall create and maintain UPS instances as instructed by creation requests and as specified by . + The SCP shall return, via the N-CREATE response primitive, the N-CREATE Response Status Code applicable to the associated request. + The SCP shall accept creation requests only if the value of the Procedure Step State (0074,1000) Attribute is "SCHEDULED". If the Procedure Step State Attribute has another value, the SCP shall fail the request. + The SCP may modify Attributes of a UPS instance, e.g., to correct invalid Attribute values. A description of the modifications the SCP may perform shall be documented in the conformance statement of the SCP. + The SCP may also create and maintain UPS instances without receiving a UPS instance N-CREATE request, e.g., based on internal logic, operator inputs or HL7 messages. The contents of the instance created by the SCP must still comply with the N-CREATE requirements in . + Upon creating a new UPS Instance, the SCP shall update UPS Subscription Status of the Instance for each AE with a Global Subscription as described in . Optionally, the SCP may create a UPS Subscription for the N-CREATE SCU AE; such behavior shall be documented in the Conformance Statement. + Upon creating a new UPS Instance, the SCP shall send UPS State Reports (if it supports the UPS Event SOP Class) as described in regardless of whether the creation was based on an N-CREATE or on internal logic. + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. There are no specific requirements to perform authorization. +
+
+ Status Codes + The status values that are specific for this DIMSE operation are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + The UPS was created as requested + + 0000 +
+ Warning + + The UPS was created with modifications + + B300 +
+ Failure + + Refused: The provided value of UPS State was not "SCHEDULED". + + C309 +
+
+
+
+ Set Unified Procedure Step Information (N-SET) + This operation allows an SCU to set Attribute Values of a UPS Instance and provide information about a specific real-world UPS that is under control of the SCU. This operation shall be invoked by the SCU through the DIMSE N-SET Service. +
+ Unified Procedure Step IOD Subset Specification + The Application Entity that claims conformance to the UPS Pull SOP Class as an SCU may choose to modify a subset of the Attributes maintained by the SCP. The Application Entity that claims conformance as an SCP to the UPS Pull SOP Class shall support Attributes specified in + +
+
+ Service Class User Behavior + The SCU shall specify in the N-SET request primitive the UID of the UPS Instance for which it wants to set Attribute Values. Since all UPSs are created as instances of the UPS Push SOP Class, the Requested SOP Class UID in the N-SET request shall be the UID of the UPS Push SOP Class. See for further details. + To N-SET a UPS instance currently in the SCHEDULED state, the Transaction UID Attribute shall not be present in the request. For a UPS instance in the IN PROGRESS state, the SCU shall provide the current Transaction UID (0008,1195) as an Attribute. + The SCU shall be permitted to set Attribute values as specified in . The SCU shall specify the list of Attributes for which it wants to set the Attribute Values. The SCU shall provide, with one or more N-SET request primitives, the Attribute values specified in . + When modifying a sequence, the SCU shall include in the N-SET request all Items in the sequence, not just the Items to be modified. + N-SET requests shall be atomic (indivisible) and idempotent (repeat executions have no additional effect). Since it is possible for an N-GET to occur between two N-SET requests, any given N-SET shall leave the UPS instance in an internally consistent state (i.e., when multiple Attributes need updating as a group, do this as multiple Attributes in a single N-SET request, not as multiple N-SET requests) + The SCU shall not set the value of the Procedure Step State (0074,1000) Attribute using N-SET. Procedure Step State is managed using N-ACTION as described in + + The SCU shall create or set all Attributes to meet Final State requirements prior to using N-ACTION to set the value of Procedure Step State (0074,1000) to "COMPLETED" or "CANCELED". See for further details. + Once the Procedure Step State (0074,1000) has been set to "COMPLETED" or "CANCELED" the SCU shall no longer modify the UPS SOP Instance. + + The SCU can only set Attribute Values that have already been created with an N-CREATE request. + +
+
+ Service Class Provider Behavior + The SOP Class UID of the specified UPS instance will always be the UPS Push SOP Class UID, which might not match the UPS SOP Class negotiated with the SCU. See for further details. + The SCP shall support the Attribute changes to the UPS instance specified by the SCU in the set request as specified in . + The SCP shall refuse set requests on an IN PROGRESS UPS and not modify the UPS if the set request does not include the Transaction UID (0008,1195) Attribute with the same value as currently recorded in the UPS instance. + The SCP shall refuse set requests on a COMPLETED or CANCELED UPS. + The SCP shall use the Specific Character Set (0008,0005) value to appropriately modify its internal representation so that subsequent operations reflect the combination of the character sets in use by the Attributes in this request and those used by Attributes that have not been modified. + The SCP shall return, via the N-SET response primitive, the N-SET Response Status Code applicable to the associated request as specified in . + The SCP may itself modify any Attributes of a UPS instance independently of an N-SET request, e.g., if the SCP is performing the procedure step itself, if it has been determined that the performing SCU has been disabled, or if it is necessary to correct Attribute values after completion of the procedure in order to carry out reconciliation of the data. A description of the coercions the SCP may perform shall be documented in the conformance statement of the SCP. + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. There are no specific requirements to perform authorization. +
+
+ Status Codes + The status values that are specific for this DIMSE operation are defined in . See for additional response status codes. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + The requested modification of the Attribute values is performed + + 0000 +
+ Warning + + Requested optional Attributes are not supported. + + 0001 +
+ Coerced invalid values to valid values + + B305 +
+ Failure + + Refused: The UPS is not in the "IN PROGRESS" state + + C310 +
+ Refused: The correct Transaction UID was not provided + + C301 +
+ Refused: The UPS may no longer be updated + + C300 +
+ Specified SOP Instance UID does not exist or is not a UPS Instance managed by this SCP + + C307 +
+
+
+
+ Get Unified Procedure Step Information (N-GET) + This operation allows an SCU to get information from an SCP about a specific real-world Procedure Step that is represented as a Unified Procedure Step Instance. This operation shall be invoked by the SCU through the DIMSE N-GET Service. +
+ Unified Procedure Step IOD Subset Specification + The Application Entity that claims conformance to the UPS Pull or UPS Watch SOP Classes as an SCU may choose to retrieve a subset of the Attribute values maintained by the SCP. The Application Entity that claims conformance as an SCP to these SOP Classes shall support the Attributes specified in . +
+
+ Service Class User Behavior + The SCU uses the N-GET to request the SCP to provide Attributes and values of a Unified Procedure Step Instance. Since all UPSs are created as instances of the UPS Push SOP Class, the Affected SOP Class UID (0000,0002) in the N-GET request shall be the UID of the UPS Push SOP Class. See for further details. + The SCU shall specify in the N-GET Service Element the UID of the SOP Instance from which Attributes are to be retrieved. + The SCU shall specify the list of Unified Procedure Step Attributes for which values are to be returned. The SCU shall not specify Attributes that are defined within a Sequence, but rather specify the sequence itself to be retrieved in its entirety. + The SCU shall not request the value of the Transaction UID (0008,1195) Attribute. + The SCU may request Attribute Values for optional Attributes that are not maintained by the SCP. In such a case, the SCU shall function properly regardless of whether the SCP returns values for those Attributes or not. This Service Class Specification places no requirements on what the SCU shall do as a result of receiving this information. + + In order to accurately interpret the character set used for the Attribute Values returned, it is recommended that the Attribute Value for the Specific Character Set (0008,0005) be requested in the N-GET request primitive. + + The SCU shall be permitted to request and shall be capable of receiving values for any Attribute as specified in . Additionally, values may be requested for optional Attributes. + The SCU shall be capable of receiving all requested Attribute Values provided by the SCP in response to the N-GET indication primitive. + + If the SCU or the user will need access to the final state Attributes it is the responsibility of the SCU to Subscribe (see ) in order to receive State Change Events and then N-GET the necessary Attributes promptly upon notification of a state change to COMPLETED or CANCELED. If the SCU sets the Deletion Lock when subscribing, a COMPLETED or CANCELED instance will continue to persist on the SCP, using resources. It is important that the SCU remove the lock (e.g., by unsubscribing) after doing the N-GET on the COMPLETED or CANCELED instance. + +
+
+ Service Class Provider Behavior + The SOP Class UID of the specified UPS instance will always be the UPS Push SOP Class UID, which might not match the UPS SOP Classes negotiated with the SCU. See for further details. + The SCP shall return, via the N-GET response primitive, the selected Attribute values from the indicated Unified Procedure Step Instance to the SCU. + + The requirement for the SCP to respond to N-GET requests for UPS Instances that have moved to the COMPLETED or CANCELED state is limited. See Service Class Provider Behavior. + + The SCP shall not return the Transaction UID (0008,1195) Attribute. This is necessary to preserve this Attribute's role as an access lock. + The SCP shall return, via the N-GET response primitive, the N-GET Response Status Code applicable to the associated request. A Failure Code shall indicate that the SCP has not retrieved the SOP Instance. + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. Further requiring or documenting authentication and/or authorization features from the SCU or SCP is beyond the scope of this SOP Class. +
+
+ Status Codes + The status values that are specific for this DIMSE operation are defined in . See for additional response status codes. + + + + + + + + + + + + + + + + + + + + + +
Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Warning + + Requested optional Attributes are not supported + + 0001 +
+ Failure + + Specified SOP Instance UID does not exist or is not a UPS Instance managed by this SCP + + C307 +
+
+
+
+ Search for Unified Procedure Step (C-FIND) + This operation allows an SCU to locate and get information about Unified Procedure Step instances of interest that are managed by an SCP. This operation shall be invoked by the SCU through the DIMSE C-FIND Service. The SCP processes such queries, matches UPS instances it manages against the keys present in the Identifier and returns C-FIND responses. + The SCU might be searching for UPS instance with the intention of starting work on one of them or perhaps with the intention of subscribing to monitor the progress of an instance. +
+ Operation +
+ E/R Model + In response to a given C-FIND request, the SCP might send several C-FIND responses, (i.e., one C-FIND response per matching worklist item). Each worklist item describes a single task and its related information. + The Unified Procedure Step Query Information Model is represented by the Entity Relationship diagram shown in . + +
+ Unified Procedure Step E-R Diagram + + + + + + +
+
+ There is only one Information Entity in the model, which is the Unified Procedure Step. The Attributes of a Unified Procedure Step can be found in . +
+
+ C-FIND Service Parameters +
+ SOP Class UID + The Affected SOP Class UID of the C-FIND DIMSE request shall always be the UPS SOP Class negotiated for the Presentation Context under which the service is requested. This will always be either the UPS Pull SOP Class or the UPS Watch SOP Class. See for further details. + For both the UPS Pull SOP Class and the UPS Watch SOP Class, the C-FIND is performed against the Unified Procedure Step Information Model shown in . +
+
+ Priority + The Priority Attribute defines the requested priority of the C-FIND operation with respect to other DIMSE operations being performed by the same SCP. + Processing of priority requests is not required of SCPs. Whether or not an SCP supports priority processing and the meaning of the different priority levels shall be stated in the Conformance Statement of the SCP. +
+
+
+ Identifier + Both the C-FIND request and response contain an Identifier encoded as a Data Set (see ). +
+ Request Identifier Structure + An Identifier in a C-FIND request shall contain: + + + Key Attributes values to be matched against the values of Attributes specified in the SOP Class identified by the Affected SOP Class UID. + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Request Identifier. It shall not be included otherwise. + + + Conditionally, the Attribute Timezone Offset From UTC (0008,0201). This Attribute shall be included if Key Attributes of time are to be interpreted explicitly in the designated local time zone. It shall not be present otherwise, i.e., it shall not be sent with a zero-length value. + + + + This means that Specific Character Set (0008,0005) is included if the SCU supports expanded or replacement character sets in the context of this service. It will not be included if expanded or replacement character sets are not supported by the SCU. + + The Key Attributes and values allowable for the query shall be defined in the SOP Class definition corresponding to the Affected SOP Class UID for the corresponding Unified Worklist And Procedure Step Information Model. +
+
+ Response Identifier Structure + The C-FIND response shall not contain Attributes that were not in the request or specified in this section. + An Identifier in a C-FIND response shall contain: + + + Key Attributes with values corresponding to Key Attributes contained in the Identifier of the request (Key Attributes as defined in .) + + + Conditionally, the Attribute Specific Character Set (0008,0005). This Attribute shall be included if expanded or replacement character sets may be used in any of the Attributes in the Response Identifier. It shall not be included otherwise. The C-FIND SCP is not required to return responses in the Specific Character Set requested by the SCU if that character set is not supported by the SCP. The SCP may return responses with a different Specific Character Set. + + + Conditionally, the Attribute Timezone Offset From UTC (0008,0201). This Attribute shall be included if any Attributes of time in the Response Identifier are to be interpreted explicitly in the designated local time zone. It shall not be present otherwise, i.e., it shall not be sent with a zero-length value. + + + + This means that Specific Character Set (0008,0005) is included if the SCP supports expanded or replacement character sets in the context of this service. It will not be included if expanded or replacement character sets are not supported by the SCP. + +
+
+
+
+ Service Class User Behavior + All C-FIND SCUs shall be capable of generating query requests that meet the requirements of the "Worklist" Search Method (see ). + Required Keys and Optional Keys, identified in , associated with the Query may be contained in the Identifier. + An SCU conveys the following semantics using the C-FIND requests and responses: + + + The SCU requests that the SCP perform a match of all keys specified in the Identifier of the request against the information it possesses of the Query specified in the request. + + + The SCU shall interpret Pending responses to convey the Attributes of a match of an item. + + + The SCU shall interpret a response with a status equal to Success, Failure, or Cancel to convey the end of Pending responses. + + + The SCU shall interpret a Failure response to a C-FIND request as an indication that the SCP is unable to process the request. + + + The SCU may cancel the C-FIND service by issuing a C-FIND-CANCEL request at any time during the processing of the C-FIND. The SCU shall recognize a status of Cancel to indicate that the C-FIND-CANCEL was successful. + + +
+
+ Service Class Provider Behavior + All C-FIND SCPs shall be capable of processing queries that meet the requirements of the "Worklist" Search (see ). This does not imply that an SCP that supports the UPS Watch SOP Class must also be an SCP of the UPS Pull SOP Class. + The SCP shall support Attribute matching as described in . + An SCP conveys the following semantics using the C-FIND requests and responses: + + + The SCP is requested to perform a match of all the keys specified in the Identifier of the request, against the information it possesses. Attribute matching is performed using the key values specified in the Identifier of the C-FIND request as defined in . + + + The SCP generates a C-FIND response for each match using the "Worklist" Search method. All such responses shall contain an Identifier whose Attributes contain values from a single match. All such responses shall contain a status of Pending. + + + When all matches have been sent, the SCP generates a C-FIND response that contains a status of Success. A status of Success shall indicate that a response has been sent for each match known to the SCP. + + + + + + No Identifier is contained in a response with a status of Success. For a complete definition, see . + + + When there are no matches, then no responses with a status of Pending are sent, only a single response with a status of Success. + + + + + + The SCP shall generate a response with a status of Failure if it is unable to process the request. A Failure response shall contain no Identifier. + + + If the SCP receives C-FIND-CANCEL indication before it has completed the processing of the matches it shall interrupt the matching process and return a status of Cancel. + + + Bi-directional Authentication of machines/users/applications is possible at association time (see and ). provides a "Refused: Not Authorized" error code. Further requiring or documenting authentication and/or authorization features from the SCU or SCP is beyond the scope of this SOP Class. +
+ Worklist Search Method + The following steps are used to generate match responses. + + + Match the key match Attributes contained in the Identifier of the C-FIND request against the values of the Key Attributes for each worklist entity. + + + If there are no matching keys, then there are no matches, return a response with a status equal to Success and with no Identifier. + + + Otherwise, + + + For each entity for which the Attributes match all of the specified matching key Attributes, construct an Identifier. This Identifier shall contain all of the values of the Attributes for this entity that correspond to the return keys specified in the C-FIND request. + + + Return a response for each remaining Identifier. + + + + + + defines the Attributes of the Unified Procedure Step Information Model, the requirements for key matching, and the requirements for return keys. +
+
+
+ Status Codes + + defines the status code values that might be returned in a C-FIND response. Fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-FIND Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources + + A700 + + (0000,0902) +
+ Identifier Does Not Match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ SOP Class not Supported + + 0122 + +
+ Unable to process + + (any value C000 through CFFF as assigned by the implementation) + + (0000,0901) + (0000,0902) +
+ Cancel + + Matching terminated due to Cancel request + + FE00 + + None +
+ Success + + Matching is complete - No final Identifier is supplied. + + 0000 + + None +
+ Pending + + Matches are continuing - Current Match is supplied and any Optional Keys were supported in the same manner as Required Keys. + + FF00 + + Identifier +
+ Matches are continuing - Warning that one or more Optional Keys were not supported for existence for this Identifier. + + FF01 + + Identifier +
+ + Status Codes are returned in DIMSE response messages (see ). The code values stated in column "Status Codes" are returned in Status Command Element (0000,0900). + +
+
+
+
+ UPS SOP Classes + There are four UPS SOP Classes associated with the Unified Procedure Step IOD. Each SOP Class supports different interactions with a UPS Instance (also referred to as a worklist item). + The UPS Push SOP Class allows SCU systems to: + + + create (push) a new worklist item (i.e., instance) onto a worklist + + + submit a cancellation request for a worklist item + + + The UPS Pull SOP Class allows SCU systems to: + + + query a worklist for matching items + + + take responsibility for performing a worklist item + + + add/modify progress/status/result details for the worklist item + + + finalize a controlled worklist item as Completed or Canceled. + + + The UPS Watch SOP Class allows SCU systems to: + + + query for worklist items of interest + + + subscribe/unsubscribe for event notifications of changes to a given worklist item + + + subscribe/unsubscribe for event notifications of all worklist items + + + get details for a given worklist item + + + submit a cancellation request for a given worklist item + + + The UPS Event SOP Class allows SCU systems to: + + + receive event notifications of changes to a worklist item + + + The DICOM AEs that claim conformance to one or more of these SOP Classes shall support all services listed as "M" in the corresponding , , and . +
+ Service Class and SOP Class UIDs + All UPS Instances shall be created with the value of SOP Class UID set to "1.2.840.10008.5.1.4.34.6.1" (i.e., that of the UPS Push SOP Class). + + UPS Instances are all based on the Unified Procedure Step IOD and are all created either internally by the SCP, or in response to an N-CREATE issued as part of the UPS Push SOP Class. + + Once created, UPS instances may be operated on by DIMSE services from any of the four UPS SOP Classes defined in the Unified Worklist and Procedure Step Service Class. + During association negotiation, the Abstract Syntax UID shall be the implemented SOP Class as shown in the following list: + + + 1.2.840.10008.5.1.4.34.6.1 (UPS Push SOP Class) + + + 1.2.840.10008.5.1.4.34.6.2 (UPS Watch SOP Class) + + + 1.2.840.10008.5.1.4.34.6.3 (UPS Pull SOP Class) + + + 1.2.840.10008.5.1.4.34.6.4 (UPS Event SOP Class) + + +
+ DIMSE Implications for UPS (Informative) + A SOP Instance may be created with one SOP Class UID (UPS Push) and later DIMSE Services may refer to it over an association negotiated for a different SOP Class UID. Further details on this can be found in . + For DIMSE-N Services, the Affected SOP Class UID (0000,0002) or Requested SOP Class UID (0000,0003), when present, will be the UID of the UPS Push SOP Class regardless of the negotiated Abstract Syntax UID. The SCU and SCP will not reject DIMSE-N messages on the basis of the Affected/Requested SOP Class UID being that of the UPS Push SOP Class, rather than one of the other three SOP Class UIDs as listed in the Abstract Syntax UID during association negotiation. The SCU and SCP may reject the DIMSE-N messages if the instance is not a UPS Push SOP Class Instance. + For DIMSE-C Services (C-FIND), the Affected SOP Class UID will always match the negotiated Abstract Syntax UID for the Presentation Context under which the request is made. This will be either UPS Watch or UPS Pull. Both of these SOP Classes represent the UPS Information Model described in . + For example, in a typical "Pull Workflow" message exchange, the C-FIND query from a "performing SCU" would use the UPS Pull SOP Class UID for both the negotiated Abstract Syntax UID and the Affected SOP Class UID (0000,0002), however the SOP Class UID (0008,0016) of the C-FIND responses themselves will be set to the UPS Push SOP Class UID by the SCP. All the subsequent N-ACTION, N-SET, and N-GET messages, would then use the UPS Pull SOP Class UID for the negotiated Abstract Syntax UID, and the UPS Push SOP Class UID for the Affected SOP Class UID (0000,0002). +
+
+ Global Instance Subscription UID + The well-known UID for subscribing/unsubscribing to events for all UPS Instances managed by an SCP shall have the value "1.2.840.10008.5.1.4.34.5". +
+
+
+ Association Negotiation + Association establishment is the first phase of any instance of communication between peer DICOM AEs. The Association negotiation procedure specified in shall be used to negotiate the supported SOP Classes. + See the Association Negotiation definition for the Basic Worklist Management Service Class (). +
+
+
+ Conformance Requirements + Implementations providing conformance to any of the UPS SOP Classes (UPS Pull, UPS Push, UPS Watch and UPS Event) shall be conformant as described in the following sections and shall include within their Conformance Statement information as described below. + An implementation may conform to any of the UPS SOP Classes as an SCU or as an SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance + An implementation, which is conformant to any of the UPS SOP Classes as an SCU, shall meet conformance requirements for the operations that it invokes. +
+ Operations + The SCU Conformance Statement shall be formatted as defined in . + An implementation, that conforms to any of the UPS Push, UPS Pull or UPS Watch SOP Classes as an SCU, shall specify under which conditions it will request the modification of the value of the Procedure Step State (0074,1000) Attribute to "IN PROGRESS", "COMPLETED", and "CANCELED". + An implementation that conforms to the UPS Pull or UPS Watch SOP Classes as an SCU shall state in its Conformance Statement + + + Whether it requests matching on Optional Matching Key Attributes for C-FIND. + + + Whether it requests Type 3 Return Key Attributes. If it requests Type 3 Return Key Attributes, then it shall list these Optional Return Key Attributes. + + + Whether or not it supports extended negotiation of fuzzy semantic matching of person names for C-FIND. + + + How it makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when encoding queries and interpreting responses for C-FIND. + + + What access mechanisms the SCP is capable of using for retrieving input data and/or making output data available. (see for details on the different Retrieval Sequences). + + +
+
+
+ SCP Conformance + An implementation that is conformant to any of the UPS SOP Classes as an SCP shall meet conformance requirements for the operations that it performs. +
+ Operations + The SCP Conformance Statement shall be formatted as defined in . + The SCP Conformance Statement shall provide information on the behavior of the SCP at the following occurrences: + + + The creation of a new Instance of the UPS Push SOP Class with the status "SCHEDULED". The result of that process on the scheduling information and on the Attribute Values of the Unified Procedure Step shall be specified. Any automatic UPS Subscription created in response to the Instance creation shall be specified. + + + The conditions for the update of the Attribute "Procedure Step State" (0074,1000), i.e., the change to the state "IN PROGRESS" or to "CANCELED" or to "COMPLETED". + + + Which Attributes the SCP may update after the state has been set to "IN PROGRESS" or "CANCELED" or "COMPLETED". + + + For how long the UPS Instance will persist on the SCP, and how long it will be available for N-GETs once its state has been set to "COMPLETED" or "CANCELED". + + + Whether the SCP supports priority for C-FIND. If the SCP supports priority for C-FIND, then the meaning of the different priority levels shall be specified. + + + Whether the SCP supports case-insensitive matching for PN VR Attributes for C-FIND. If the SCP supports case-insensitive matching of PN VR Attributes, then the Attributes for which this applies shall be specified. + + + Whether the SCP supports extended negotiation of fuzzy semantic matching of person names for C-FIND. If the SCP supports extended negotiation of fuzzy semantic matching of person names, then the mechanism for fuzzy semantic matching shall be specified. + + + How the SCP makes use of Specific Character Set (0008,0005) and Timezone Offset From UTC (0008,0201) when interpreting C-FIND queries, performing matching and encoding responses. + + + What rules the SCP may use to perform additional filtering during a C-FIND (e.g., limiting returns based on the requesting user and the confidentiality settings of the workitems, or limiting the return to a single item already selected on the SCP) and under what conditions those rules are invoked. + + + Whether the SCP might refuse Subscription requests and/or Deletion Locks and for what reasons. + + + What access mechanisms the SCP is capable of using for retrieving input data and/or making output data available. (see for details on the different Retrieval Sequences). + + +
+
+
+
+ + RT Machine Verification Service Classes (Normative) +
+ Scope + The RT Machine Verification Service Classes define an application-level class-of-service that facilitates the independent verification of geometric and dosimetric settings on a radiation delivery system prior to delivery of a radiation treatment. The service classes are intended for use with both conventional (e.g., photon, electron) as well as particle therapy (e.g., proton, ion) treatments. +
+
+ RT Machine Verification Model +
+ RT Machine Verification Data Flow + In the RT Machine Verification Model, the Service Class User (SCU) of the applicable Machine Verification Service Class is the radiation delivery system used to administer the treatment. The Machine Parameter Verifier (MPV) acts in the role of Service Class Provider (SCP). + The communication states between the SCU and SCP can be described in two levels shown in : A) the Plan Level and B) the Beam Level. + The first level (A in the diagram) is the Plan Level. The SCU initializes external verification of a new plan using the N-CREATE command. The MPV then retrieves the data necessary to perform verification through DICOM or other means. In general, there is a close relationship between an MPV and a Treatment Management System (TMS) or Archive. If DICOM is the protocol used to retrieve this data, this might be done using one or more C-MOVEs on the Archive. + The second level (B in the diagram) is the Beam Level. The SCU uses the N-SET command request to instruct the SCP on the specified Attributes to be verified. The SCU then requests that the verification start using an N-ACTION command. The SCP compares the values of the specified Attributes against the values of the Attributes from the referenced plan, and signals the status of the verification using N-EVENT-REPORT command with the Treatment Verification Status (3008,002C) Attribute indicating the verification result. The MPV's use of tolerance values in the verification process shall be described in a Conformance Statement. The SCU may then optionally request the beam's verification parameters using an N-GET. + Finally, when all beams have been delivered or abandoned, the SCU terminates the verification session at the Plan Level using an N-DELETE. + +
+ RT Verification Data Flow + + + + + + +
+
+
+
+
+ Machine Verification SOP Class Definitions +
+ IOD Description + The Machine Verification IODs are abstractions of the information needed to verify the correct setup of a treatment delivery system prior to radiation treatment. +
+
+ DIMSE Service Group + + shows DIMSE Services applicable to the IODs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DIMSE Service Group
+ + DIMSE Service Element + + + + Usage SCU/SCP + +
+ N-CREATE + + M/M +
+ N-SET + + M/M +
+ N-GET + + M/M +
+ N-ACTION + + M/M +
+ N-DELETE + + M/M +
+ N-EVENT-REPORT + + M/M +
+ The meaning of the Usage SCU/SCP is described in . + This Section describes the behavior of the DIMSE Services that are specific for this IOD. The general behavior of the DIMSE services is specified in . +
+ N-CREATE and N-SET + The N-CREATE is used to create an instance of the applicable Machine Verification SOP Class. + The N-SET is used to communicate parameters for verification to an MPV by setting Attributes on an instance of the applicable Machine Verification SOP Class. + All Attributes in the table relating to the number of a certain item (e.g., Number of Wedges, Number of Control Points) specify the number in the N-SET command. The numbering in the Beams Verification Request is not necessarily the same as the numbering in the referenced RT Plan. +
+ Attributes + The Attribute list of the N-CREATE and N-SET for the RT Conventional Machine Verification SOP Class is shown in . See Section 5.4 for usage notation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-CREATE and N-SET Attribute List - RT Conventional Machine Verification SOP Class
+ + Attribute Name + + + + Tag + + + + N-CREATE Usage SCU/SCP + + + + N-SET Usage SCU/SCP + +
+ + + RT General Machine Verification Module + + +
+ Referenced RT Plan Sequence + + (300C,0002) + + 1/1 + (only a single Item shall be permitted) + + Not allowed +
+ >Referenced SOP Class UID + + (0008,1150) + + 1/1 + + Not allowed +
+ >Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + Not allowed +
+ Referenced Fraction Group Number + + (300C,0022) + + 1C/1 + (required if plan has more than one fraction group) + + Not allowed +
+ Patient ID + + (0010,0020) + + 1/1 + + Not allowed +
+ + Include + + +
+ Treatment Verification Status + + (3008,002C) + + Not allowed + + Not allowed +
+ Failed Parameters Sequence + + (0074,1048) + + Not allowed + + Not allowed +
+ Overridden Parameters Sequence + + (0074,104A) + + Not allowed + + Not allowed +
+ General Machine Verification Sequence + + (0074,1042) + + 2/2 + (sequence shall contain zero items) + + 1/1 + (only a single Item shall be permitted) +
+ >Specified Primary Meterset + + (3008,0032) + + -/- + + 3/3 +
+ >Specified Secondary Meterset + + (3008,0033) + + -/- + + 3/3 +
+ >Specified Treatment Time + + (3008,003A) + + -/- + + 3/3 +
+ >Beam Limiting Device Leaf Pairs Sequence + + (3008,00A0) + + -/- + + 3/3 +
+ >>RT Beam Limiting Device Type + + (300A,00B8) + + -/- + + 1/1 +
+ >>Number of Leaf/Jaw Pairs + + (300A,00BC) + + -/- + + 1/1 +
+ >Recorded Wedge Sequence + + (3008,00B0) + + -/- + + 2C/2C + (required if MPV is capable of verifying wedges). See . +
+ >>Wedge Number + + (300A,00D2) + + -/- + + 1/1 +
+ >>Wedge ID + + (300A,00D4) + + -/- + + 3/3 +
+ >>Wedge Angle + + (300A,00D5) + + -/- + + 3/3 +
+ >>Wedge Orientation + + (300A,00D8) + + -/- + + 3/3 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >Recorded Compensator Sequence + + (3008,00C0) + + -/- + + 2C/2C + (required if MPV is capable of verifying compensators). See . +
+ >>Compensator ID + + (300A,00E5) + + -/- + + 3/3 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Referenced Compensator Number + + (300C,00D0) + + -/- + + 1/1 +
+ >Recorded Block Sequence + + (3008,00D0) + + -/- + + 2C/2C + (required if MPV is capable of verifying blocks). See . +
+ >>Block Tray ID + + (300A,00F5) + + -/- + + 3/3 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Referenced Block Number + + (300C,00E0) + + -/- + + 1/1 +
+ >Treatment Machine Name + + (300A,00B2) + + -/- + + 1/1 +
+ >Beam Name + + (300A,00C2) + + -/- + + 3/3 +
+ >Radiation Type + + (300A,00C6) + + -/- + + 1/1 +
+ >Number of Wedges + + (300A,00D0) + + -/- + + 1/1 +
+ >Number of Compensators + + (300A,00E0) + + -/- + + 1/1 +
+ >Number of Boli + + (300A,00ED) + + -/- + + 1/1 +
+ >Number of Blocks + + (300A,00F0) + + -/- + + 1/1 +
+ >Applicator Sequence + + (300A,0107) + + -/- + + 2C/2C + (required if MPV is capable of verifying applicators). See . +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Applicator ID + + (300A,0108) + + -/- + + 3/3 +
+ >>Applicator Type + + (300A,0109) + + -/- + + 1/1 +
+ >Number of Control Points + + (300A,0110) + + -/- + + 1/1 + (value shall be 1) +
+ >Patient Setup Sequence + + (300A,0180) + + -/- + + 3/3 + (one or more Items may be included) +
+ >>Patient Setup Number + + (300A,0182) + + -/- + + 1/1 +
+ >>Fixation Device Sequence + + (300A,0190) + + -/- + + 2C/2C + (required if MPV is capable of verifying fixation devices). See . +
+ >>>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>>Fixation Device Type + + (300A,0192) + + -/- + + 1/1 +
+ >Referenced Beam Number + + (300C,0006) + + -/- + + 1/1 +
+ >Referenced Bolus Sequence + + (300C,00B0) + + -/- + + 2C/2C + (required if MPV is capable of verifying bolus). See . +
+ >>Referenced ROI Number + + (3006,0084) + + -/- + + 1/1 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ + All other Attributes of the + + + + - + + -/- + + 3/3 +
+ + + RT Conventional Machine Verification Module + + +
+ Conventional Machine Verification Sequence + + (0074,1044) + + 2/2 + (sequence shall contain zero items) + + 1/1 + (only a single Item shall be permitted) +
+ >Conventional Control Point Verification Sequence + + (0074,104C) + + -/- + + 1/1 + (only a single Item shall be permitted) +
+ >>Nominal Beam Energy + + (300A,0114) + + -/- + + 3/3 +
+ >>Dose Rate Set + + (300A,0115) + + -/- + + 3/3 +
+ >>Wedge Position Sequence + + (300A,0116) + + -/- + + 1C/1C + (required if Number of Wedges (300A,00D0) is non-zero,one or more Items may be included) +
+ >>>Wedge Position + + (300A,0118) + + -/- + + 1/1 +
+ >>>Referenced Wedge Number + + (300C,00C0) + + -/- + + 1/1 +
+ >>Beam Limiting Device Position Sequence + + (300A,011A) + + -/- + + 1C/1C + (required if Beam Limiting Device Leaf Pairs Sequence (3008,00A0) is sent,one or more Items may be included) +
+ >>>RT Beam Limiting Device Type + + (300A,00B8) + + -/- + + 1/1 +
+ >>>Leaf/Jaw Positions + + (300A,011C) + + -/- + + 1/1 +
+ >>Gantry Angle + + (300A,011E) + + -/- + + 3/3 +
+ >>Gantry Rotation Direction + + (300A,011F) + + -/- + + 2/2 +
+ >>Beam Limiting Device Angle + + (300A,0120) + + -/- + + 3/3 +
+ >>Beam Limiting Device Rotation Direction + + (300A,0121) + + -/- + + 3/3 +
+ >>Patient Support Angle + + (300A,0122) + + -/- + + 3/3 +
+ >>Patient Support Rotation Direction + + (300A,0123) + + -/- + + 3/3 +
+ >>Table Top Eccentric Axis Distance + + (300A,0124) + + -/- + + 3/3 +
+ >>Table Top Eccentric Angle + + (300A,0125) + + -/- + + 3/3 +
+ >>Table Top Eccentric Rotation Direction + + (300A,0126) + + -/- + + 3/3 +
+ >>Table Top Vertical Position + + (300A,0128) + + -/- + + 3/3 +
+ >>Table Top Longitudinal Position + + (300A,0129) + + -/- + + 3/3 +
+ >>Table Top Lateral Position + + (300A,012A) + + -/- + + 3/3 +
+ >>Table Top Pitch Angle + + (300A,0140) + + -/- + + 3/3 +
+ >>Table Top Pitch Rotation Direction + + (300A,0142) + + -/- + + 3/3 +
+ >>Table Top Roll Angle + + (300A,0144) + + -/- + + 3/3 +
+ >>Table Top Roll Rotation Direction + + (300A,0146) + + -/- + + 3/3 +
+ >>Referenced Control Point Index + + (300C,00F0) + + -/- + + 1/1 +
+ + All other Attributes of the + + + + - + + -/- + + 3/3 +
+ The Attribute list of the N-CREATE and N-SET for the RT Ion Machine Verification SOP Class is shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-CREATE and N-SET Attribute List - RT Ion Machine Verification SOP Class
+ + Attribute Name + + + + Tag + + + + N-CREATE Usage SCU/SCP + + + + N-SET Usage SCU/SCP + +
+ + + RT General Machine Verification Module + + +
+ Referenced RT Plan Sequence + + (300C,0002) + + 1/1 + (only a single Item shall be permitted) + + Not allowed +
+ >Referenced SOP Class UID + + (0008,1150) + + 1/1 + + Not allowed +
+ >Referenced SOP Instance UID + + (0008,1155) + + 1/1 + + Not allowed +
+ Referenced Fraction Group Number + + (300C,0022) + + 1C/1 + (required if plan has more than one fraction group) + + Not allowed +
+ Patient ID + + (0010,0020) + + 1/1 + + Not allowed +
+ + Include + + +
+ Treatment Verification Status + + (3008,002C) + + Not allowed + + Not allowed +
+ Failed Parameters Sequence + + (0074,1048) + + Not allowed + + Not allowed +
+ Overridden Parameters Sequence + + (0074,104A) + + Not allowed + + Not allowed +
+ General Machine Verification Sequence + + (0074,1042) + + 2/2 + (sequence shall contain zero items) + + 1/1 + (only a single Item shall be permitted) +
+ >Specified Primary Meterset + + (3008,0032) + + -/- + + 3/3 +
+ >Specified Secondary Meterset + + (3008,0033) + + -/- + + 3/3 +
+ >Specified Treatment Time + + (3008,003A) + + -/- + + 3/3 +
+ >Beam Limiting Device Leaf Pairs Sequence + + (3008,00A0) + + -/- + + 3/3 + See . +
+ >>RT Beam Limiting Device Type + + (300A,00B8) + + -/- + + 1/1 +
+ >>Number of Leaf/Jaw Pairs + + (300A,00BC) + + -/- + + 1/1 +
+ >Recorded Wedge Sequence + + (3008,00B0) + + -/- + + 2C/2C + (required if MPV is capable of verifying wedges). See . +
+ >>Wedge Number + + (300A,00D2) + + -/- + + 1/1 +
+ >>Wedge ID + + (300A,00D4) + + -/- + + 3/3 +
+ >>Wedge Angle + + (300A,00D5) + + -/- + + 3/3 +
+ >>Wedge Orientation + + (300A,00D8) + + -/- + + 3/3 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >Recorded Compensator Sequence + + (3008,00C0) + + -/- + + 2C/2C + (required if MPV is capable of verifying compensators). See . +
+ >>Compensator ID + + (300A,00E5) + + -/- + + 3/3 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Referenced Compensator Number + + (300C,00D0) + + -/- + + 1/1 +
+ >Recorded Block Sequence + + (3008,00D0) + + -/- + + 2C/2C + (required if MPV is capable of verifying blocks). See . +
+ >>Block Tray ID + + (300A,00F5) + + -/- + + 3/3 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Referenced Block Number + + (300C,00E0) + + -/- + + 1/1 +
+ >Treatment Machine Name + + (300A,00B2) + + -/- + + 1/1 +
+ >Beam Name + + (300A,00C2) + + -/- + + 3/3 +
+ >Radiation Type + + (300A,00C6) + + -/- + + 1/1 +
+ >Number of Wedges + + (300A,00D0) + + -/- + + 1/1 +
+ >Number of Compensators + + (300A,00E0) + + -/- + + 1/1 +
+ >Number of Boli + + (300A,00ED) + + -/- + + 1/1 +
+ >Number of Blocks + + (300A,00F0) + + -/- + + 1/1 +
+ >Applicator Sequence + + (300A,0107) + + -/- + + 2C/2C + (required if MPV is capable of verifying applicators). See . +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Applicator ID + + (300A,0108) + + -/- + + 3/3 +
+ >>Applicator Type + + (300A,0109) + + -/- + + 1/1 +
+ >Number of Control Points + + (300A,0110) + + -/- + + 1/1 + (value shall be 1) +
+ >Patient Setup Sequence + + (300A,0180) + + -/- + + 3/3 + See . +
+ >>Patient Setup Number + + (300A,0182) + + -/- + + 1/1 +
+ >>Fixation Device Sequence + + (300A,0190) + + -/- + + 2C/2C + (required if MPV is capable of verifying fixation devices). See . +
+ >>>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>>Fixation Device Type + + (300A,0192) + + -/- + + 1/1 +
+ >Referenced Beam Number + + (300C,0006) + + -/- + + 1/1 +
+ >Referenced Bolus Sequence + + (300C,00B0) + + -/- + + 2C/2C + (required if MPV is capable of verifying bolus). See . +
+ >>Referenced ROI Number + + (3006,0084) + + -/- + + 1/1 +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ + All other Attributes of the + + + + - + + -/- + + 3/3 +
+ + + RT Ion Machine Verification Module + + +
+ Ion Machine Verification Sequence + + (0074,1046) + + 2/2 + (sequence shall contain zero items) + + 1/1 + (only a single Item shall be permitted) +
+ >Ion Control Point Verification Sequence + + (0074,104E) + + -/- + + 1/1 + (only a single Item shall be permitted) +
+ >>Meterset Rate Set + + (3008,0045) + + -/- + + 3/3 +
+ >>Nominal Beam Energy + + (300A,0114) + + -/- + + 3/3 +
+ >>Beam Limiting Device Position Sequence + + (300A,011A) + + -/- + + 1C/1C + (required if Beam Limiting Device Leaf Pairs Sequence (3008,00A0) is sent,one or more Items may be included) +
+ >>>RT Beam Limiting Device Type + + (300A,00B8) + + -/- + + 1/1 +
+ >>>Leaf/Jaw Positions + + (300A,011C) + + -/- + + 1/1 +
+ >>Gantry Angle + + (300A,011E) + + -/- + + 3/3 +
+ >>Gantry Rotation Direction + + (300A,011F) + + -/- + + 2/2 +
+ >>Beam Limiting Device Angle + + (300A,0120) + + -/- + + 3/3 +
+ >>Beam Limiting Device Rotation Direction + + (300A,0121) + + -/- + + 3/3 +
+ >>Patient Support Angle + + (300A,0122) + + -/- + + 3/3 +
+ >>Patient Support Rotation Direction + + (300A,0123) + + -/- + + 3/3 +
+ >>Table Top Vertical Position + + (300A,0128) + + -/- + + 3/3 +
+ >>Table Top Longitudinal Position + + (300A,0129) + + -/- + + 3/3 +
+ >>Table Top Lateral Position + + (300A,012A) + + -/- + + 3/3 +
+ >>Table Top Pitch Angle + + (300A,0140) + + -/- + + 3/3 +
+ >>Table Top Pitch Rotation Direction + + (300A,0142) + + -/- + + 3/3 +
+ >>Table Top Roll Angle + + (300A,0144) + + -/- + + 3/3 +
+ >>Table Top Roll Rotation Direction + + (300A,0146) + + -/- + + 3/3 +
+ >>Head Fixation Angle + + (300A,0148) + + -/- + + 3/3 +
+ >>Gantry Pitch Angle + + (300A,014A) + + -/- + + 3/3 +
+ >>Gantry Pitch Rotation Direction + + (300A,014C) + + -/- + + 3/3 +
+ >>Snout Position + + (300A,030D) + + -/- + + 3/3 +
+ >>Range Shifter Settings Sequence + + (300A,0360) + + -/- + + 1C/1C + (required if Number of Range Shifters (300A,0312) is non-zero,one or more Items may be included) +
+ >>>Range Shifter Setting + + (300A,0362) + + -/- + + 1/1 +
+ >>>Referenced Range Shifter Number + + (300C,0100) + + -/- + + 1/1 +
+ >>Lateral Spreading Device Settings Sequence + + (300A,0370) + + -/- + + 1C/1C + (required if Number of Lateral Spreading Devices (300A,0330) is non-zero,one or more Items may be included) +
+ >>>Lateral Spreading Device Setting + + (300A,0372) + + -/- + + 1/1 +
+ >>>Referenced Lateral Spreading Device Number + + (300C,0102) + + -/- + + 1/1 +
+ >>Range Modulator Settings Sequence + + (300A,0380) + + -/- + + 1C/1C + (required if Number of Range Modulators (300A,0340) is non-zero,one or more Items may be included) +
+ >>>Range Modulator Gating Start Value + + (300A,0382) + + -/- + + 1/1 +
+ >>>Range Modulator Gating Stop Value + + (300A,0384) + + -/- + + 1/1 +
+ >>>Referenced Range Modulator Number + + (300C,0104) + + -/- + + 1/1 +
+ >>Ion Wedge Position Sequence + + (300A,03AC) + + -/- + + 1C/1C + (required if Number of Wedges (300A,00D0) is non-zero,one or more Items may be included) +
+ >>>Wedge Thin Edge Position + + (300A,00DB) + + -/- + + 1C/1C + (required if Wedge Type (300A,00D3) of the wedge referenced by Referenced Wedge Number (300C,00C0) is PARTIAL_STANDARD or PARTIAL_MOTORIZ) +
+ >>>Wedge Position + + (300A,0118) + + -/- + + 1/1 +
+ >>Referenced Control Point Index + + (300C,00F0) + + -/- + + 1/1 +
+ >Recorded Snout Sequence + + (3008,00F0) + + -/- + + 1C/1C + (required if Snout Sequence is included in the RT Ion Plan referenced within the Referenced RT Plan Sequence (300C,0002); only a single Item is permitted in this sequence) +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Snout ID + + (300A,030F) + + -/- + + 3/3 +
+ >Recorded Range Shifter Sequence + + (3008,00F2) + + -/- + + 2C/2C + (required if MPV is capable of verifying range shifters). See . +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Range Shifter ID + + (300A,0318) + + -/- + + 3/3 +
+ >>Referenced Range Shifter Number + + (300C,0100) + + -/- + + 1/1 +
+ >Recorded Lateral Spreading Device Sequence + + (3008,00F4) + + -/- + + 2C/2C + (required if MPV is capable of verifying lateral spreading devices). See . +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Lateral Spreading Device ID + + (300A,0336) + + -/- + + 3/3 +
+ >>Referenced Lateral Spreading Device Number + + (300C,0102) + + -/- + + 1/1 +
+ >Recorded Range Modulator Sequence + + (3008,00F6) + + -/- + + 2C/2C + (required if MPV is capable of verifying range modulators). See . +
+ >>Accessory Code + + (300A,00F9) + + -/- + + 3/3 +
+ >>Range Modulator ID + + (300A,0346) + + -/- + + 3/3 +
+ >>Range Modulator Type + + (300A,0348) + + -/- + + 1/1 +
+ >>Beam Current Modulation ID + + (300A,034C) + + -/- + + 1C/1C + (required if Range Modulator Type (300A,0348) is WHL_MODWEIGHTS) +
+ >>Referenced Range Modulator Number + + (300C,0104) + + -/- + + 1/1 +
+ >Radiation Mass Number + + (300A,0302) + + -/- + + 1C/1C + (required if Radiation Type (300A,00C6) is ION) +
+ >Radiation Atomic Number + + (300A,0304) + + -/- + + 1C/1C + (required if Radiation Type (300A,00C6) is ION) +
+ >Radiation Charge State + + (300A,0306) + + -/- + + 1C/1C + (required if Radiation Type (300A,00C6) is ION) +
+ >Scan Mode + + (300A,0308) + + -/- + + 1/1 +
+ >Number of Range Shifters + + (300A,0312) + + -/- + + 1/1 +
+ >Number of Lateral Spreading Devices + + (300A,0330) + + -/- + + 1/1 +
+ >Number of Range Modulators + + (300A,0340) + + -/- + + 1/1 +
+ >Patient Support Type + + (300A,0350) + + -/- + + 3/3 +
+ >Patient Support ID + + (300A,0352) + + -/- + + 3/3 +
+ >Patient Support Accessory Code + + (300A,0354) + + -/- + + 3/3 +
+ >Fixation Light Azimuthal Angle + + (300A,0356) + + -/- + + 3/3 +
+ >Fixation Light Polar Angle + + (300A,0358) + + -/- + + 3/3 +
+ + All other Attributes of the + + + + - + + -/- + + 3/3 +
+
+ Beam Modifiers + If the MPV is not capable of performing the type of verification required by the Attribute, then the Attribute shall not be present. If the MPV is capable of performing the type of verification required by the Attribute, then the Attribute will be zero length if there are no such modifiers, and valued with one or more items if there are one or more such modifiers. +
+
+
+ Status + The status values for N-CREATE that are specific for these SOP Classes are defined as follows: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RT Ion Machine Verification SOP Class N-CREATE Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Machine Verification successfully created + + 0000 +
+ Failure + + No such object instance - Referenced RT Plan not found + + C227 +
+ The Referenced Fraction Group Number does not exist in the referenced plan + + C221 +
+ No beams exist within the referenced fraction group + + C222 +
+ SCU already verifying and cannot currently process this request. + + C223 +
+ The status values for N-SET that are specific for these SOP Classes are defined as follows: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
RT Ion Machine Verification SOP Class N-SET Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Machine Verification successfully updated + + 0000 +
+ Failure + + Referenced Beam Number not found within the referenced Fraction Group + + C224 +
+ Referenced device or accessory not supported + + C225 +
+ Referenced device or accessory not found within the referenced beam + + C226 +
+
+
+ Behavior +
+ N-CREATE + The SCU uses N-CREATE to request the SCP to create an applicable Machine Verification SOP Instance. The SCP shall create the SOP Instance and shall initialize Attributes of the SOP Class. + The General Machine Verification Sequence, Conventional Machine Verification Sequence, and Ion Machine Verification Sequence are created with an empty value, and specification of the contained Attributes is deferred until the N-SET operation. + The SCP shall return the status code of the requested SOP Instance creation. The meaning of success, warning and failure status codes is defined in . +
+
+ N-SET + The SCU uses the N-SET to request the SCP to update an applicable Machine Verification instance. The SCU shall specify the SOP Instance to be updated and shall specify the list of Attributes for which the Attribute Values are to be set. The Attributes in the Conventional/Ion Control Point Verification Sequence represent the Treatment Delivery System's actual geometric values at the time the N-SET request is issued and therefore, the Conventional/Ion Control Point Verification Sequence shall always contain one sequence item. The Referenced Control Point Index shall be zero for NORMAL treatments, and may be greater than zero for CONTINUATION treatments. + Within an Attribute sequence such as the General Machine Verification Sequence, Conventional Machine Verification Sequence, and Ion Machine Verification Sequence, values for all required Attributes must be supplied with each N-SET, or else the missing Attributes will have any previously set values removed from the SOP Instance. Existing parameters may be cleared by sending an empty sequence or Attribute. The MPV's Conformance Statement shall specify the set of Attributes that it requires for verification. + The SCU shall set the new values for the specified Attributes of the specified SOP Instance. The SCP shall then compare the values of Attributes of the specified SOP Instance to the values of the same Attributes found in the RT Plan referenced in N-CREATE. Values shall be compared using the tolerance values also found in the referenced RT Plan. The result of this comparison shall be available for use when the SCU requests the Treatment Verification Status using an N-GET. +
+
+
+
+ N-GET + The N-GET is used to get the verification status and results of the applicable Machine Verification SOP Class. +
+ Parameters Selector Attribute Macro + + describes N-GET support requirements for the Selector Attribute Macro. See Section 5.4 for requirements type code meaning. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Verification Parameters Selector Attribute Macro
+ + Attribute Name + + + + Tag + + + + Req. Type N-GET (SCU/SCP) + +
+ Selector Attribute + + (0072,0026) + + -/1 +
+ Selector Value Number + + (0072,0028) + + -/1 +
+ Selector Sequence Pointer + + (0072,0052) + + -/1 +
+ Selector Sequence Pointer Private Creator + + (0072,0054) + + -/1 +
+ Selector Sequence Pointer Items + + (0074,1057) + + -/1 +
+ Selector Attribute Private Creator + + (0072,0056) + + -/1 +
+
+
+ Attributes + The Attribute list of the N-GET for the RT Conventional Machine Verification SOP Class and RT Ion Machine Verification SOP Class is shown in . See Section 5.4 for usage notation. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
N-GET Attribute List- RT Conventional Machine Verification SOP Class and RT Ion Machine Verification SOP Class
+ + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Referenced RT Plan Sequence + + (300C,0002) + + -/1 +
+ >Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ >Referenced SOP Instance UID + + (0008,1155) + + -/1 +
+ Referenced Fraction Group Number + + (300C,0022) + + -/1 +
+ Patient ID + + (0010,0020) + + -/1 +
+ + Include + + +
+ Treatment Verification Status + + (3008,002C) + + -/1 +
+ Failed Parameters Sequence + + (0074,1048) + + -/2 + (zero or more items shall be included in this Sequence) +
+ + >Include + + +
+ Overridden Parameters Sequence + + (0074,104A) + + -/2 + (zero or more items shall be included in this Sequence) +
+ + >Include + + +
+ >Operators' Name + + (0008,1070) + + -/2 +
+ >Override Reason + + (3008,0066) + + -/2 +
+ + All other Attributes + + + - + + 3/2 +
+
+
+ Status + The status values that are specific for these SOP Classes are defined as follows: + + + + + + + + + + + + + + + + + + + + + +
RT Conventional Machine and RT Ion Machine Verification SOP Class N-GET Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Treatment Verification Status of the applicable Machine Verification instance successfully returned. + + 0000 +
+ Failure + + No such object instance - applicable Machine Verification instance not found + + C112 +
+
+
+ Behavior + The SCU uses N-GET to retrieve from the SCP the verification status and results of the applicable Machine Verification SOP Instance. + The SCP shall return the Treatment Verification Status (3008,002C) Attribute as well as the status code of the requested SOP Instance update. Treatment Verification Status shall have one of the following values: + VERIFIED = treatment verified + VERIFIED_OVR = treatment verified with at least one out-of-range value overridden + NOT_VERIFIED = verification of treatment was not successful + The VERIFIED state indicates that all required parameters have been checked and no out-of-range values have been detected. The VERIFIED_OVR state indicates that the treatment failed to verify due to one or more out-of-range values that were then overridden. NOT_VERIFIED indicates that one of more of the out-of-range values has not yet been overridden and the treatment cannot go ahead. This could be because at least one out-of-range value was detected, or one or more values required for verification were not supplied. The site- and vendor-specific configuration of the MPV determines the Attributes and ranges required for successful verification. + If the Treatment Verification Status is VERIFIED_OVR, one or more parameter occurrences shall be returned in Overridden Parameters Sequence (0074,104A), otherwise the sequence shall be empty. + If the Treatment Verification Status is NOT_VERIFIED, one or more parameter occurrences shall be returned in Failed Parameters Sequence (0074,1048), otherwise the sequence shall be empty. + See for specification of how the Attribute tags and position within a sequence are encoded. + The SCP shall return the status code of the requested action. The meanings of success, warning and failure status codes are defined in . +
+
+
+ N-ACTION + The N-ACTION is used to initiate parameter verification of an instance of the applicable Machine Verification SOP Class. +
+ Attributes + The action types of the N-ACTION are defined as shown in . + + + + + + + + + + + + + + + + + + + + +
Action Event Information
+ + Action Type Name + + + + Action Type ID + + + + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Request Beam Verification + + 1 + + None + + - + + - +
+
+
+ Status + The status values that are specific for these SOP Classes are defined in . + + + + + + + + + + + + + + + + + + + + + +
RT Conventional Machine and RT Ion Machine Verification SOP Class N-ACTION Status Values
+ + Status + + + + Meaning + + + + Code + +
+ Success + + Machine Parameter Verification of the applicable Machine Verification instance successfully initiated. + + 0000 +
+ Failure + + No such object instance - Machine Verification requested instance not found. + + C112 +
+
+
+ Behavior + The SCU uses N-ACTION to instruct the SCP to initiate machine parameter verification of the applicable Machine Verification SOP Instance. +
+
+
+ N-DELETE + The N-DELETE is used to delete an instance of the applicable Machine Verification SOP Class. +
+ Attributes + There are no specific Attributes. +
+
+ Status + There are no specific status codes. +
+
+ Behavior + The SCU uses the N-DELETE to request the SCP to delete an applicable Machine Verification SOP Instance. The SCU shall specify in the N-DELETE request primitive the SOP Instance UID of the applicable Machine Verification instance. + The SCP shall delete the specified SOP Instance, such that subsequent operations of the same SOP Instance will fail. + The SCP shall return the status code of the requested SOP Instance deletion. The meanings of success, warning, and failure status classes are defined in . + If an N-DELETE is not issued, the SOP Class instance may be deleted on the SCP by a manual or automatic operation. This behavior is beyond the scope of the standard. +
+
+
+ N-EVENT-REPORT + The N-EVENT-REPORT is used by the MPV to notify the TDS of the status of the verification task (successful or otherwise), or to notify the TDS that a verification is pending (in progress). The encoding of Notification Event Information is defined in . +
+ Attributes + The arguments of the N-EVENT-REPORT are defined as shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Notification Event Information
+ + Event Type Name + + + + Event Type ID + + + + Attribute Name + + + + Tag + + + + Usage SCU/SCP + +
+ Pending + + 1 + + None + + - + + - +
+ Done + + 2 + + Treatment Verification Status + + (3008,002C) + + -/1 +
+
+
+ Status + There are no specific status codes. +
+
+ Behavior + The SCP uses the N-EVENT-REPORT to inform the SCU of the verification status. See . + If the Event Type ID = 1 then the verification is still in progress, and the SCU must wait until another event is received. See . + If the Event Type ID = 2 then the verification process has been completed. The SCU may use the returned value of the Treatment Verification Status (3008,002C) to determine whether or not the beam is ready to be delivered, or if a machine adjustment or override needs to be made. See . +
+
+
+
+
+ + + Display System Management Service Class (Normative) +
+ Scope + The Display System Service Class allows service users retrieve parameters related to the Display Subsystem(s). + +
+ Display System Management Data Flow + + + + + + +
+
+
+
+ Display System SOP Class +
+ IOD Description + The Display System IOD is an abstraction of the soft-copy display system and is the basic Information Entity to monitor the status of a Display System. The Display System SOP Instance is created by the SCP during start-up of the Display System and has a well-known SOP Instance UID. +
+
+ DIMSE Service Group + The DIMSE Service shown in is applicable to the Display System IOD under the Display System SOP Class. + + + + + + + + + + + + + + +
DIMSE Service Group - Display System
+ DIMSE Service Element + + Usage SCU/SCP +
+ N-GET + + M/M +
+ This section describes the behavior of the DIMSE services that are specific for this IOD. The general behavior of the DIMSE services is specified in . +
+ N-GET + N-GET is used to retrieve information from an instance of the Display System SOP class. +
+ Attributes +
+ Display Subsystem Macros + To reduce the size and complexity of , a macro notation is used. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Table Result Context Macro
+ Attribute Name + + Tag + + Usage SCU/SCP +
+ Performed Procedure Step Start DateTime + + (0040,4050) + + -/1 +
+ Performed Procedure Step End DateTime + + (0040,4051) + + -/1 +
+ Actual Human Performer Sequence + + (0040,4035) + + 3/2 +
+ >Human Performer Code Sequence + + (0040,4009) + + -/1C + (Required if Human Performer's Name (0040,4037) is not present.) +
+ + >>Include + + +
+ >Human Performer's Name + + (0040,4037) + + -/1C + (Required if Human Performer Code Sequence (0040,4009) is not present.) +
+ >Human Performer's Organization + + (0040,4036) + + -/2 +
+ Measurement Equipment Sequence + + (0028,7012) + + 3/2 +
+ >Measurement Functions + + (0028,7013) + + -/1 +
+ >Measured Characteristics + + (0028,7026) + + -/1 +
+ >Measurement Equipment Type + + (0028,7014) + + -/1 +
+ >Manufacturer + + (0008,0070) + + -/1 +
+ >Manufacturer's Model Name + + (0008,1090) + + -/1 +
+ >Device Serial Number + + (0018,1000) + + -/2 +
+ >DateTime of Last Calibration + + (0018,1202) + + -/2 +
+
+
+ Display System N-GET Attribute Requirements + The attributes that may be retrieved are shown in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Display System N-GET Attributes
+ Attribute Name + + Tag + + Usage SCU/SCP +
+ Specific Character Set + + (0008,0005) + + -/1C + (Required if an extended or replacement character set is used) +
+ + Display System Module + +
+ Manufacturer + + (0008,0070) + + 3/1 +
+ Institution Name + + (0008,0080) + + 3/1 +
+ Institution Address + + (0008,0081) + + 3/1 +
+ Device Serial Number + + (0018,1000) + + 3/1 +
+ Station Name + + (0008,1010) + + 3/2 +
+ Institutional Department Name + + (0008,1040) + + 3/2 +
+ Manufacturer's Model Name + + (0008,1090) + + 3/1 +
+ Equipment Administrator Sequence + + (0028,7000) + + 3/2 +
+ >Person Name + + (0040,A123) + + -/2 +
+ >Person Identification Code Sequence + + (0040,1101) + + -/1 +
+ + >>Include + + +
+ >Person's Address + + (0040,1102) + + -/3 +
+ >Person's Telephone Numbers + + (0040,1103) + + -/3 +
+ >Institution Name + + (0008,0080) + + -/1C + (Required if Institution Code Sequence (0008,0082) is not present. May be present otherwise.) +
+ >Institution Address + + (0008,0081) + + -/3 +
+ >Institution Code Sequence + + (0008,0082) + + -/1C + (Required if Institution Name (0008,0080) is not present.May be present otherwise.) +
+ + >>Include + + +
+ Number of Display Subsystems + + (0028,7001) + + 3/1 +
+ Display Subsystem Sequence + + (0028,7023) + + 3/1 +
+ >Display Subsystem ID + + (0028,7003) + + -/1 +
+ >Display Subsystem Name + + (0028,7004) + + -/2 +
+ >Display Subsystem Description + + (0028,7005) + + -/2 +
+ >Display Device Type Code Sequence + + (0028,7022) + + -/2 +
+ + >>Include + + +
+ >Manufacturer + + (0008,0070) + + -/2 +
+ >Device Serial Number + + (0018,1000) + + -/2 +
+ >Manufacturer's Model Name + + (0008,1090) + + -/2 +
+ >System Status + + (0028,7006) + + -/1 +
+ >System Status Comment + + (0028,7007) + + -/2 +
+ >Display Subsystem Configuration Sequence + + (0028,700A) + + -/2 +
+ >>Configuration ID + + (0028,700B) + + -/1 +
+ >>Configuration Name + + (0028,700C) + + -/2 +
+ >>Configuration Description + + (0028,700D) + + -/2 +
+ >>Referenced Target Luminance Characteristics ID + + (0028,700E) + + -/2 +
+ >Current Configuration ID + + (0028,7002) + + -/2 +
+ >Measurement Equipment Sequence + + (0028,7012) + + -/2 +
+ >>Measurement Functions + + (0028,7013) + + -/1 +
+ >>Measured Characteristics + + (0028,7026) + + -/1 +
+ >>Measurement Equipment Type + + (0028,7014) + + -/1 +
+ >>Manufacturer + + (0008,0070) + + -/1 +
+ >>Manufacturer's Model Name + + (0008,1090) + + -/1 +
+ >>Device Serial Number + + (0018,1000) + + -/1 +
+ >>DateTime of Last Calibration + + (0018,1202) + + -/2 +
+ + Target Luminance Characteristics Module + +
+ Target Luminance Characteristics Sequence + + (0028,7008) + + 2/1 +
+ >Luminance Characteristics ID + + (0028,7009) + + -/1 +
+ >Display Function Type + + (0028,7019) + + -/1 +
+ >Target Minimum Luminance + + (0028,701D) + + -/1 +
+ >Target Maximum Luminance + + (0028,701E) + + -/1 +
+ >Gamma Value + + (0028,701A) + + -/1C + (Required if the value of Display Function Type (0028,7019) is GAMMA) +
+ >Number of Luminance Points + + (0028,701B) + + -/1C + (Required if the value of Display Function Type (0028,7019) is USER_DEFINED) +
+ >Luminance Response Sequence + + (0028,701C) + + -/1C + (Required if the value of Display Function Type (0028,7019) is USER_DEFINED) +
+ >>DDL Value + + (0028,7017) + + -/1 +
+ >>Luminance Value + + (0028,701F) + + -/1 +
+ >Luminance Response Description + + (0028,7020) + + -/1C + (Required if the value of Display Function Type (0028,7019) is USER_DEFINED. May be present otherwise.) +
+ >CIExy White Point + + (0028,7018) + + -/3 +
+ >Reflected Ambient Light + + (2010,0160) + + -/3 +
+ >Ambient Light Value Source + + (0028,7025) + + -/1C + (Required if Reflected Ambient Light (2010,0160) is present.) +
+ + QA Results Module + +
+ QA Results Sequence + + (0028,700F) + + 3/1 +
+ >Display Subsystem ID + + (0028,7003) + + -/1 +
+ >Display Subsystem QA Results Sequence + + (0028,7010) + + -/2 +
+ >>Configuration ID + + (0028,700B) + + -/1 +
+ >> Configuration QA Results Sequence + + (0028,7011) + + -/2 +
+ >>>Display Calibration Result Sequence + + (0028,7016) + + -/2 +
+ + >>>>Include + + +
+ >>>>Luminance Characteristics ID + + (0028,7009) + + -/1 +
+ >>>Visual Evaluation Result Sequence + + (0028,7015) + + -/2 +
+ + >>>>Include + + +
+ >>>>Visual Evaluation Test Sequence + + (0028,7028) + + -/1 +
+ >>>>>Test Result + + (0028,7029) + + -/1 +
+ >>>>>Test Result Comment + + (0028,702A) + + -/3 +
+ >>>>>Test Pattern Code Sequence + + (0028,702C) + + -/3 +
+ + >>>>>>Include + + +
+ >>>>>Referenced Image Sequence + + (0008,1140) + + -/1C + (Required if Test Pattern Code Sequence (0028,702C) is absent in this item.May be present otherwise.) +
+ >>>>>>Referenced SOP Class UID + + (0008,1150) + + -/1 +
+ >>>>>>Referenced SOP Instance UID + + (0008,1151) + + -/1 +
+ >>>>>>Referenced Frame Number + + (0008,1160) + + -/1C + (Required if the Referenced SOP Instance is a multi-frame image and the reference does not apply to all frames, and Referenced Segment Number (0062,000B) is not present.) +
+ >>>>>>Referenced Segment Number + + (0062,000B) + + -/1C + (Required if the Referenced SOP Instance is a Segmentation or Surface Segmentation and the reference does not apply to all segments and Referenced Frame Number (0008,1160) is not present.) +
+ >>>>>>Test Image Validation + + (0028,702B) + + -/3 +
+ >>>>Visual Evaluation Method Code Sequence + + (0028,702E) + + -/1 +
+ + >>>>>Include + + +
+ >>>Luminance Uniformity Result Sequence + + (0028,7027) + + -/2 +
+ + >>>>Include + + +
+ >>>>Number of Luminance Points + + (0028,701B) + + -/1 +
+ >>>>Measurement Pattern Code Sequence + + (0028,702D) + + -/1 +
+ + >>>>>Include + + +
+ >>>>DDL Value + + (0028,7017) + + -/1 +
+ >>>>White Point Flag + + (0028,7021) + + -/1 +
+ >>>>Luminance Response Sequence + + (0028,701C) + + -/1 +
+ >>>>>Luminance Value + + (0028,701F) + + -/1 +
+ >>>>>CIExy White Point + + (0028,7018) + + -/1C + (Required if the value of White Point Flag (0028,7021) is YES.) +
+ >>>>Reflected Ambient Light + + (2010,0160) + + -/3 +
+ >>>>>Ambient Light Value Source + + (0028,7025) + + -/1C + (Required if Reflected Ambient Light (2010,0160) is present.) +
+ >>>Luminance Result Sequence + + (0028,7024) + + -/2 +
+ + >>>>Include + + +
+ >>>>Number of Luminance Points + + (0028,701B) + + -/1 +
+ >>>>Luminance Response Sequence + + (0028,701C) + + -/1 +
+ >>>>>DDL Value + + (0028,7017) + + -/1 +
+ >>>>>Luminance Value + + (0028,701F) + + -/1 +
+ >>>>>CIExy White Point + + (0028,7018) + + -/3 +
+ >>>>Reflected Ambient Light + + (2010,0160) + + -/3 +
+ >>>>Ambient Light Value Source + + (0028,7025) + + -/1C + (Required if Reflected Ambient Light (2010,0160) is present.) +
+
+
+
+ SCU Behavior + The SCU uses the N-GET to request the SCP to provide the contents of a Display System SOP Instance. The SCU shall specify in the N-GET request primitive the UID of the SOP Instance from which attributes are to be returned. + The SCU shall specify the list of Display System Attributes for which values are to be returned. The SCU shall not specify Attributes which are defined within a Sequence, but rather specify the sequence itself to be returned in its entirety. + The SCU shall specify in the N-GET request primitive the well-known UID of the SOP Instance. +
+
+ SCP Behavior + The SCP shall return the values for the specified Attributes of the Display System SOP Instance. + The SCP shall return the status code for the requested SOP Instance retrieval. The meaning of success, warning, and failure status codes are defined in . +
+
+
+
+ SOP Class Definitions and UIDs + The SOP Class UID of the Display System SOP class shall have the value of "1.2.840.10008.5.1.1.40". +
+
+ Reserved Identifications + The well-known UID of the Display System SOP Instance shall have the value of "1.2.840.10008.5.1.1.40.1". +
+
+
+ Conformance +
+ Conformance Statement + The implementation conformance statement of this SOP class shall follow . + The SCU Conformance Statement shall specify the following items: + + + Maximum number of associations to be supported at the same time + + + List of SOP Classes supported + + + For each of the supported SOP classes: + + + List of supported SOP class attributes and DIMSE service elements + + + For each supported attribute (mandatory and optional), a valid value range + + + + + The SCP Conformance Statement shall specify the following items: + + + Maximum number of associations to be supported at the same time + + + List of SOP Classes supported + + + For each of the supported SOP classes: + + + List of supported SOP class attributes and DIMSE service elements + + + For each supported attribute (mandatory and optional) + + + Valid value range + + + Default value if no value is supplied by the SCU + + + Status code (Failure or Warning) if the SCU supplies a value that is out of range + + + + + + + For each supported DIMSE service + + + SCP behavior for all specific status codes + + + + +
+
+
+ + + Volumetric Presentation State Storage SOP Classes (Normative) +
+ Overview +
+ Scope + The Volumetric Presentation State Storage SOP Classes extend the functionality of the Storage Service class (defined in ) to add the ability to convey an intended Volumetric Presentation State or record an existing Volumetric Presentation State. The SOP Classes specify the information and behavior that may be used to present (display) images that are referenced from within the SOP Classes. + They include capabilities for specifying: + + + spatial registration on the input datasets + + + cropping of the volume datasets by a bounding box, oblique planes and segmentation objects + + + the generation geometry of volumetric views + + + shading models + + + scalar to P-Value or RGB Value conversions + + + compositing of multiple MPR renderings + + + compositing of multiple volume streams and one volume stream with segmentation + + + clinical description of the specified view + + + volume and display relative annotations, including graphics, text and overlays plus optional references to structured content providing clinical context for annotations + + + membership to a collection of related Volumetric Presentation States intended to be processed or displayed together + + + the position within a set of sequentially related Volumetric Presentation States + + + animation of the view + + + reference to an image depicting the view described by the Volumetric Presentation State + + + Each Volumetric Presentation State corresponds to a single view (equivalent to an Image Box in a Hanging Protocol or Structured Display). If multiple Volumetric Presentation States are intended to be displayed together (e.g., a set of orthogonal MPR views) these Presentation States can be grouped by assigning them to a Display Collection. However, any detailed information about how a set of views should be presented can only be described by a Structured Display instance or a Hanging Protocol. + The Planar MPR Volumetric Presentation State refers to the multi-planar geometry and grayscale or color image transformations that are to be applied in an explicitly defined manner to convert the stored image pixel data values in a Composite Image Instance to presentation values (P-Values) or Profile Connection Space values (PCS-Values) when an image is displayed on a softcopy device. + The Volume Rendering Volumetric Presentation State specifies a volume rendered view of volume data. Volume Rendering is a data visualization method in which voxels (volume sample points) are assigned a color and an opacity (alpha), and a 2D view is created by accumulating a set of non-transparent samples along a ray through the volume behind each pixel of the view. Ray samples are calculated by interpolating the voxel values in the neighborhood of each sample. + Volume Rendering generally consists of a number of steps, many of which are parametrically specified in the Volume Rendering SOP Classes. The processing steps are: + + + Segmentation, or separating the volume data into groups that will share a particular color palette. Segmentation objects are specified as cropping inputs to the Volumetric Presentation State. + + + Gradient Computation, or finding edges or boundaries between different types of tissue in the volumetric data. The gradient computation method used is an implementation decision outside the scope of the Volumetric Presentation State. + + + Resampling of the volumetric data to create new samples along the imaginary ray behind each pixel in the output two-dimensional view, generally using some interpolation of the values of voxels in the neighborhood of the new sample. The interpolation method used is an implementation decision outside the scope of the Volumetric Presentation State. + + + Classification of samples to assign a color and opacity to each sample. + + + Shading or the application of a lighting model to samples indicating the effect of ambient, diffuse, and specular light on the sample. + + + Compositing or the accumulation of samples on each ray into the final value of the pixel corresponding to that ray. The specific algorithms used are outside the scope of the Volumetric Presentation State. + + + Conversion to presentation Profile Connection Space values (PCS-Values) when an image is displayed on a softcopy device. + + + The result of applying a Volumetric Presentation State is not expected to be exactly reproducible on different systems. It is difficult to describe the display and rendering algorithms in enough detail in an interoperable manner such that a presentation produced at a later time is indistinguishable from that of the original presentation. While Volumetric Presentation States use established DICOM concepts of grayscale and color matching (GSDF and ICC color profiles) and provide a generic description of the different types of display algorithms possible, variations in algorithm implementations within display devices are inevitable and an exact match of volume presentation on multiple devices cannot be guaranteed. Nevertheless, reasonable consistency is provided by specification of inputs, geometric descriptions of spatial views, type of processing to be used, color mapping and blending, input fusion, and many generic + rendering parameters, producing what is expected to be a clinically acceptable result. + The P-Values are in a device independent perceptually linear space that is formally defined in . The PCS-Values are in a device independent space that is formally defined in the ICC Profiles as CIEXYZ or CIELab values. + How an SCP of these SOP Classes chooses between multiple Presentation State instances that may apply to an image is beyond the scope of this standard. + A claim of conformance as an SCP of the SOP Class implies that the SCP shall make the Presentation State available to the user of the device, and if selected by the user, shall apply all the transformations stored in the state in the manner in which they are defined in the standard. + How an SCP of these SOP Classes chooses to display multiple states that are part of a Display Collection is beyond the scope of this standard. + + For example, if a user selects a state that is part of a four state Spatial Collection, an SCP may choose to display all four together, to display the single state selected by the user or to display two of the four states deemed appropriate by the SCP. + +
+
+
+ Volume Transformation Processes +
+ Volumetric Transformations + The transformations defined in the Volumetric Presentation State Storage SOP Classes replace those that may be defined in the Referenced Image SOP Instances. If a particular transformation is absent in a Volume Rendering Volumetric Presentation State Storage SOP Instance, then it shall be assumed to be an identity transformation and any equivalent transformation, if present, in the Referenced Image SOP Instances shall not be used. + The presentation-related Attributes of the Volume Rendering Volumetric Presentation State Storage SOP Classes are immutable. They shall never be modified or updated; only a derived SOP Instance with a new SOP Instance UID may be created to represent a different presentation. +
+ Planar MPR Volumetric Transformations + The Planar MPR Volumetric Presentation State Storage SOP Classes support a set of transformations to produce derived volumetric views of volume input data. + The Grayscale Planar MPR Volumetric Presentation State Storage SOP Class defines a grayscale volumetric view from a single volume input. The sequence of transformations from volumetric inputs into P-Values is explicitly defined in the reference pipeline described in . + +
+ Grayscale Planar MPR Volumetric Pipeline + + + + + + + +
+
+ The Compositing Planar MPR Volumetric Presentation State Storage SOP Class defines a true color volumetric view from one or more volume inputs. The sequence of transformations from volumetric inputs into PCS-Values is explicitly defined in the reference pipeline described in . The actual sequence implemented may differ (such as classifying and compositing prior to creating the MPR view) but must result in similar appearance. + +
+ Compositing Planar MPR Volumetric Pipeline + + + + + + +
+
+ The planar MPR transformation requires a volume that is in the Volumetric Presentation State Reference Coordinate System (VPS-RCS). + MPR generation is based on the attributes of the Multi-Planar Reconstruction Geometry Module (see ). If the MPR Thickness Type (0070,1502) is SLAB then the Rendering Method (0070,120D) is also used. + If Pixel Presentation (0008,9205) is MONOCHROME, then Presentation LUT Shape (2050,0020) provides the transform to output P-Values. + If Pixel Presentation (0008,9205) is TRUE_COLOR, then Presentation State Classification Component Sequence (0070,1801) describes the conversion of each processed input into an RGB data stream, and Presentation State Compositor Component Sequence (0070,1805) describes the compositing of these separate RGBA data streams into a single RGB data stream. This single RGB data stream is then processed as described by ICC Profile (0028,2000) to produce output PCS-Values. +
+
+ Volume Rendering Volumetric Transformations +
+ Volume Rendering Pipelines + The Volume Rendering Volumetric Presentation State Storage SOP Classes support a set of transformations to produce derived volumetric views of volume input data. Attributes comprising the Volume Rendering Volumetric Presentation States are defined in the context of the reference pipelines described in this section. While the reference pipelines imply a certain order of the volume rendering operations of classification, resampling, shading, and compositing, the specific order in which these operations are applied by any device claiming conformance to this standard are implementation-dependent and beyond the scope of this standard. It is the responsibility of the viewing application to transform the standard attributes into parameters appropriate for the particular order of operations implemented in the viewing application. + The Volume Rendering Volumetric Presentation State Storage SOP Class defines a volumetric view from a single volume input to produce a volume rendered view. The sequence of transformations from volumetric inputs into PCS-Values is explicitly defined in the reference pipeline described in . + +
+ Volume Rendering Volumetric Pipeline + + + + + + +
+
+ The Segmented Volume Rendering Volumetric Presentation State Storage SOP Class defines a volumetric view from a single volume dataset with optional segmentation croppings, each colored separately and blended into the volume to be rendered. The sequence of transformations from volumetric inputs into PCS-Values is explicitly defined in the reference pipeline described in . + There is a single item in the Volume Stream Sequence (0070,1A08) for instances of this SOP Class. + The classified segmented volumes shall be blended in lowest to highest priority order using B-over-A blending of the RGB data and the corresponding opacity (alpha) data. The first item in the Presentation State Classification Component Sequence (0070,1801) is the base upon which subsequent items are cropped and B-over-A blended with it. + +
+ Segmented Volume Rendering Volumetric Pipeline + + + + + + +
+
+ The Multiple Volume Rendering Volumetric Presentation State Storage SOP Class defines a volumetric view from more than one volume input. The sequence of transformations from volumetric inputs into PCS-Values is explicitly defined in the reference pipeline described in . The specific algorithms for volume rendering may differ, but must result in a similar appearance. + It is expected that all volume inputs are spatially registered to the Volumetric Presentation State - Reference Coordinate System. The specific step in the processing at which resampling is performed to achieve this spatial registration is an implementation decision. + Each item in the Volume Stream Sequence (0070,1A08) produces one input to a RGBA Compositor. + +
+ Multiple Volume Rendering Volumetric Pipeline + + + + + + +
+
+ Transformation to PCS-Values is performed after Volume Rendering. +
+
+ Volume Rendering Component + This component transforms an RGBA volume into a volume rendered view according to the parameters in the Render Geometry Module. This component is implementation dependent, but generally includes processing steps such as gradient computation to find normals of use in the shading operation, resampling of volume data, shading according to the parameters in the Render Shading Module, and compositing of the resampled data to produce the final volume rendered view. +
+
+ Graphic Projection Component + This component converts the volumetric annotation specified in the Volumetric Graphic Annotation module into a graphic overlay for the 2D volume rendered view. It is the role of this component to evaluate the volumetric graphic annotations, determine which graphics are visible in the volume rendered view, and provide graphics that are layered on the view. + Inputs to the Graphic Projection component are: + + + Volumetric Graphic Annotation Module + + + RGBA volume input to the Volume Rendering component + + + Volume Render Geometry Module + + + Input-specific Cropping Specification Index (0070,1205) values + + + Volume Cropping Module Attributes + + + The Graphic Projection transform algorithm considers whether each volumetric graphic annotation is visible in the current volume rendered view, considering the volume data, Volume Render Geometry, and the value of Annotation Clipping (0070,1907). + If Annotation Clipping (0070,1907) is YES, then the annotation shall be visible only if it is present in the field of view and not obscured by opaque structures that may lie between the annotation and the viewpoint. In the case of the Volumetric Presentation Input Annotation Sequence (0070,1905), annotation text shall be visible only if some part of the specified segmentation is visible. + If Annotation Clipping (0070,1907) is NO, then the annotation shall always be visible. A particular implementation may display annotations that lie behind opaque structures in a different style (such as a softer gray), but the decision to provide such display style is outside the scope of this standard. + The output of the Graphic Projection component is displayed on the 2D presentation view in the graphic layers specified by the corresponding values of Graphic Layer (0070,0002). +
+
+
+
+ Volumetric Inputs, Registration and Cropping + A Volumetric Presentation State can take multiple volumes as input. A volume is defined in . The same source data can be referenced in more than one input. + The VOI LUT encoded in the Volumetric Presentation State is applied to the input data. + The input volumes may or may not be in the Volumetric Presentation State Reference Coordinate System (VPS-RCS). If they are not, they shall be registered into the VPS-RCS. + Two methods of cropping the input volumes are provided: + + + All inputs to the Volumetric Presentation State may be cropped using the common cropping methods specified by Global Crop (0070,120B) and items in the Volume Cropping Sequence (0070,1301). + + + In addition, cropping may be specified independently for each input to the Volumetric Presentation State as specified by the value of Crop (0070,1204) and items in the Volume Cropping Sequence (0070,1301). + + + + Combinations of cropping methods may be specified. For example, all inputs could be cropped using global bounding box cropping in addition to another cropping method applied to one of more individual inputs to the Volumetric Presentation State. + +
+
+ Volumetric Presentation State Display +
+ Volumetric Presentation State Display Overview + The MPR Volumetric Presentation State Display Module defines the algorithms used to transform the result of the MultiPlanar Reconstruction volumetric processing on the input data into an output of P-Values or PCS-Values for display. + The Render Display Module defines the algorithms used to transform the result of the Volume Rendering processing on the input data into output RGBA values. Presentation State Classification Component Sequence (0070,1801) describes the conversion of each cropped input into an RGBA volumetric data stream. Volume Stream Sequence (0070,1209) describes RGBA volumetric data streams which are overlayed using ordered "B over A" blending into a volumetric data stream. Presentation State Compositor Component Sequence (0070,1805) describes how the “B over A” blended volumetric data streams are to be composited together into a single RGBA volumetric data stream. This single RGBA data stream is an input to the Volume Rendering component. +
+
+ Description of Display Components +
+ Classification Component Components + There are two classification component types currently defined for conversion from scalar input data to RGBA. The defined components are: + + + One Input -> RGBA: This component accepts reconstructed data from one input in the Volumetric Presentation State Input Sequence (0070,1201) and generates an RGB and an Alpha output. This classification component would be specified in an item of the Presentation State Classification Component Sequence (0070,1801): + +
+ One Input -> RGBA Component + + + + + + +
+
+
+ + Two Inputs -> RGBA: This component accepts reconstructed data from two inputs in the Volumetric Presentation State Input Sequence (0070,1201) and generates an RGB and an Alpha output. This component is used in the case where a two-dimensional color mapping needs to be performed. This classification component would be specified in an item of the Presentation State Classification Component Sequence (0070,1801): + +
+ Two Inputs ->RGBA Component + + + + + + +
+
+ + An example for the use of this component is to combine Ultrasound Flow Velocity and Ultrasound Flow Variance to produce a color range from red-blue based on flow velocity and adding a yellow-green tinge based on flow variance) + +
+
+
+
+ Compositor Components + There are two compositor component types defined for compositing of two input RGBA (or one RGBA and one RGB) data sources. The defined components are: + + + RGB Compositor: This component accepts two RGBA inputs (with one Alpha input optional) and composites the data into a single RGB output. Each item of Presentation State Compositor Component Sequence (0070,1805) specifies one RGB Compositor component: + +
+ RGB Compositor Component + + + + + + +
+
+
+ + RGBA Compositor: This component accepts two RGBA inputs and composites the data into a single RGB output and a single Alpha output. + +
+ RGBA Compositor Component + + + + + + +
+
+
+
+
+
+
+ Internal Structure of Components +
+ Internal Structure of Classification Components + Component Type (0070,1802) specifies the component defined in each item of Presentation State Classification Component Sequence (0070,1801), which in turn controls by conditions the rest of the content of the item to provide the necessary specification of the component. The internal structure of each component in block diagram form is as follows: + + + One Input -> RGBA: Specified by Component Type (0070,1802) = ONE_TO_RGBA: + +
+ Internal Structure of One Input -> RGBA Component + + + + + + +
+
+
+ + Two Inputs -> RGBA: If Component Type (0070,1802) = TWO_TO_RGBA: + +
+ Internal Structure of Two Input -> RGBA Component + + + + + + +
+
+
+
+ The number of most significant bits extracted from each input is specified by the value of Bits Mapped to Color Lookup Table (0028,1403) in the Component Input Sequence (0070,1803) item for that input. + If Component Type (0070,1802) = TWO_TO_RGBA, there shall be two items in Component Input Sequence (0070,1803) with the first item defining the source of the most significant bits of the Palette Color Lookup Table input and the second item defining the source of the least significant bits of the Palette Color Lookup Table input +
+
+ Internal Structure of RGB and RGBA Compositor Components + Weighting transfer functions that compute the weighting factors used by the Compositor Function as a function of Alpha1 and Alpha2 values are specified as weighting look-up tables (LUTs) in the RGB and RGBA Compositor components. The RGB and RGBA Compositor components are identical except for the compositing of the additional Alpha component in the RGBA Compositor: + +
+ Internal Structure of RGB Compositor Component + + + + + + +
+
+ +
+ Internal Structure of RGB and Opacity Compositor Component + + + + + + +
+
+ Because each Weighting LUT uses both Alpha values in determining a weighting factor, they allow compositing functions that would not be possible if each weighting factor were based only on that input's Alpha value. See for typical usage of the Weighting LUTs. + The input bits to the Weighting LUTs are obtained by combining the two Alpha inputs, with half the input bits obtained from each Alpha input: + + + In the case of the first compositor component corresponding to the first item in Presentation State Compositor Component Sequence (0070,1805), the Alpha from the classification component corresponding to the first item in the Presentation State Classification Component Sequence (0070,1805) provides the most significant bits of the Weighting LUT inputs, while the Alpha from the classification component corresponding to the second item in the Presentation State Classification Component Sequence (0070,1805) provides the least significant bits of the Weighting LUT inputs. + + + In the case of subsequent compositor components, the Alpha from the classification component corresponding to the next item in the Presentation State Classification Component Sequence (0070,1805) provides the least significant bits of the Weighting LUT inputs, while the most significant bits of the Weighting LUT inputs are computed as one minus the Alpha from the classification component corresponding to the next item in the Presentation State Classification Component Sequence (0070,1805). + + + The integer outputs of the Weighting LUTs are normalized to the range 0.0 to 1.0, and the Compositor Function combines the normalized R, G, B and Alpha (each component called "Color" = Cx) input values as follows: + + Cout = (C1*Weight1) + (C2*Weight2) + + The sum of the normalized Weight1 and Weight2 shall be no greater than 1.0. + The color input values are normalized because the number of output bits from the RGB Palette Color Lookup Tables and the Alpha Palette Color Lookup Table may be different in each classification component. + The output of the compositor shall be range-limited ("clamped") to ensure that the outputs are guaranteed to be within a valid range of color values regardless of the validity of the weighting transfer functions. This isolates subsequent compositor components and the Profile Connection Space Transform from overflow errors. +
+
+
+
+ Additional Volumetric Considerations +
+ Annotations in Volumetric Presentations States + The Volumetric Presentation States provide two ways for annotating views: + + + Annotations on the Volumetric Presentation View + + + Annotations described by coordinates in the Volumetric Presentation State Reference Coordinate System (VPS-RCS) with optional references to Structured Reports providing context. + + + Annotations on the view provide the application of free unformatted text or vector graphics as described in the . Since the Graphic Annotation Module allows only the addition of graphics to the 2D view defined by the Presentation State without attached clinical meaning, Volumetric Graphic Annotations provide a mechanism to create annotations in the VPS-RCS with optional references to other objects which can have structured context attached. + Volumetric Graphic Annotations can be specified in two variants: either via Graphic Types with 3D coordinates, as defined in , or via a reference to inputs of the Presentation State. The latter is intended to be used to display annotation labels for segmentations of the volume data set; for example, when a lesion has been marked via a Segmentation IOD and this segmentation is rendered together with the anatomical data. + Since annotations which are added via the Graphic Annotation Module are defined within the display space, they should not be used to point to clinical relevant structures which would be positioned on a different anatomy after manipulation. + In contrast since Volumetric Graphic Annotations have coordinates in the VPS-RCS, applications can still show them after a user has manipulated the initial view which has been defined by the Presentation State. + The exact visual representation of the annotations is at the discretion of the display application, as well as the mechanisms which may be employed to ensure that Volumetric Graphic Annotations are sufficiently visible, even if the location in the volume is not visible in the current view. E.g. for a Graphic Type POINT a display application might render a crosshair at the specified position in the volume or a sphere with an arrow pointing to it instead of rendering Volumetric Graphic Annotations directly within the volume a projection of the annotations may be rendered as an overlay on top of the view. + However, annotations can be grouped into Graphic Layers and it is suggested that applications provide mechanisms to define rendering styles per Graphic Layer. + See and for examples of Volumetric Graphic Annotations. +
+
+ Volumetric Animation + Several different styles of animation are defined in Volumetric Presentation States. In general, an animation style will vary either the input, processing, or view geometry in order to produce a varying presentation view. This section describes each of the animation styles and how it produces an animated view. +
+ Input Sequence Animation + A Presentation Animation Style (0070,1A01) value of INPUT_SEQ indicates that Input Sequence Animation is being specified. In this animation style, a single Volumetric Presentation State is defined which includes input items in the Volumetric Presentation State Input Sequence (0070,1201) with different values of Input Sequence Position Index (0070,1203). The animated presentation view is produced by sequencing through values of Input Sequence Position Index (0070,1203) at a specified animation rate Recommended Animation Rate (0070,1A03), where each value of the index produces one 'frame' of the animated view from inputs that have that value of Input Sequence Position Index (0070,1203). See . + + For example, a set of inputs could be temporally related volumes of a moving anatomical structure like the heart. + + There may be more than one input item in Volumetric Presentation State Input Sequence (0070,1201) with the same value of Input Sequence Position Index (0070,1203), in which case the inputs are processed together to produce the frame of the animated view. + + For example, pairs of input items could represent the same volume input at a point in time with two different segmentation croppings (representing different organ structures) that are blended together into a single view. + + +
+ Input Sequence Animation + + + + + + +
+
+
+
+ Presentation Sequence Animation + A Presentation Animation Style (0070,1A01) value of PRESENTATION_SEQ indicates that Presentation Sequence Animation is being specified. In this animation style, a set of Volumetric Presentation States are applied sequentially. See . + + One example of the use of presentation sequence animation is a view of a moving heart wherein a stent is at a stationary position at the center of the view. Because the geometry of each view frame is slightly different, separate Volumetric Presentation State instances are required for each view frame. + + Each Volumetric Presentation State of the set is identified by having the same value of Presentation Sequence Collection UID (0070,1102). The order of application of these Presentation States is determined by the value of Presentation Sequence Position Index (0070,1103) defined in the Presentation State. The animated presentation view is produced by sequencing through values of presentation sequence position index at a specified animation rate Recommended Animation Rate (0070,1A03), where each value of the index produces one 'frame' of the animated view produced by that Volumetric Presentation State. + +
+ Presentation Sequence Animation + + + + + + +
+
+
+
+ Crosscurve Animation + A Presentation Animation Style (0070,1A01) value of CROSSCURVE indicates that Crosscurve Animation is being specified. In this animation style, a Presentation State defines a Planar MPR view at the beginning of a curve defined in Animation Curve Sequence (0070,1A04). The Planar MPR view is stepped a distance Animation Step Size (0070,1A05) along the curve defined in Animation Curve Sequence (0070,1A04) at the rate specified by Recommended Animation Rate (0070,1A03) in steps per second. See . + + A typical application of this animation style is motion along a curve centered within the colon or a blood vessel. + + +
+ Crosscurve Animation + + + + + + +
+
+
+
+ Flythrough Animation + A Presentation Animation Style (0070,1A01) value of FLYTHROUGH indicates that Flythrough Animation is being specified. In this animation style, the Volumetric Presentation State defines an initial volume rendered view and a specified movement of the view along a path through the volume. See . + +
+ Flythrough Animation + + + + + + +
+
+
+
+ Swivel Animation + A Presentation Animation Style (0070,1A01) value of SWIVEL indicates that Swivel Animation is being specified. In this animation style, a Presentation State defines an initial volume rendered using Viewpoint Position (0070,1603), Viewpoint LookAt Point (0070,1604) and Viewpoint Up Direction (0070,1605). When the animation begins, the view begins to rotate back and forth about an axis parallel to the Viewpoint Up Direction (0070,1605) that intersects the Viewpoint LookAt Point (0070,1604). The extent of the arc of rotation is defined by Swivel Range (0070,1A06) and the maximum rate of rotation is specified by Recommended Animation Rate (0070,1A03) in degrees per second, although it is recommended that the changes of direction at the ends of the swivel range be smooth which implies a slowing of the rotation as the endpoints are approached. +
+
+
+ Display Layout + The layout of multiple Volumetric Presentation States is not specified by the Volumetric Transformation process. However, there are attributes within Volumetric Presentation States that can influence the overall display layout. + For instance: + + + Anatomic Region Sequence (0008,2218) specifies the anatomic region covered by the Volumetric Presentation State + + + View Code Sequence (0054,0220) describes the view of the anatomic region of interest (e.g., Coronal, Oblique transverse, etc.) + + + Presentation Display Collection UID (0070,1101) identifies the Presentation State as one of a set of views intended to be displayed together + + + SOP Class UID (0008,0016) identifies that the Presentation State describes the volumetric view + + + The use of these attributes allows a display application to create an appropriate presentation of multiple Volumetric Presentation States, whether through the application of a Hanging Protocol instance, a Structured Display instance or by means of an application-specific algorithm. + For an example of their use, see . +
+
+
+
+ Behavior of An SCP + In addition to the behavior for the Storage Service Class specified in , the following additional requirements are specified for the Volumetric Presentation State Storage SOP Classes: + + + a display device acting as an SCP of these SOP Classes shall make all mandatory presentation attributes available for application to the referenced volumetric data at the discretion of the display device user, for all Image Storage SOP Classes defined in the Conformance Statement for which the Volumetric Presentation State Storage SOP Class is supported. + + + a display device acting as an SCP of the Volumetric Presentation State Storage SOP Classes shall support the Segmentation SOP Class for cropping and the Spatial Registration SOP Class for registration. + + + a display device acting as an SCP of a Volume Rendering Volumetric Presentation State Storage SOP Class shall perform an unshaded volume rendering if the Render Shading Module is absent from the SOP Instance. + + + a display device acting as an SCP of the Volumetric Presentation State Storage SOP Classes is not required to support the Presentation Animation Module. + + + a display device acting as an SCP of any of the Volumetric Presentation State Storage SOP Classes is not required to support Structured Reporting Storage SOP Classes. + + +
+
+ Conformance + In addition to the Conformance Statement requirements for the Storage Service Class specified in , the following additional requirements are specified for the Volumetric Presentation State Storage SOP Classes: +
+ Conformance Statement For An SCU + The following behavior shall be documented in the Conformance Statement of any implementation claiming conformance to a Volumetric Presentation State Storage SOP Class as an SCU: + + + For an SCU of a Volumetric Presentation State Storage SOP Class that is creating a SOP Instance of the Class, the manner in which presentation related attributes are derived from a displayed image, operator intervention or defaults, and how they are included in the IOD. + + + For an SCU of a Volumetric Presentation State Storage SOP Class, the Image Storage SOP Classes that are also supported by the SCU and which may be referenced by instances of the Volumetric Presentation State Storage SOP Class. + + +
+
+ Conformance Statement For An SCP + The following behavior shall be documented in the Conformance Statement of any implementation claiming conformance to a Volumetric Presentation State Storage SOP Class as an SCP: + + + For an SCP of a Volumetric Presentation State Storage SOP Class that is displaying an image referred to by a SOP Instance of the Class, the manner in which presentation related attributes are used to influence the display of an image. + + + For an SCP of a Volumetric Presentation State Storage SOP Class, the Image Storage SOP Classes that are also supported by the SCP and which may be referenced by instances of the Volumetric Presentation State Storage SOP Class. + + + For an SCP of a Volumetric Presentation State Storage SOP Class, whether the Presentation Animation Module is supported, and if not supported, any notifications or lack of notifications to the user that the context information is not displayed. + + + For an SCP of a Volumetric Presentation State Storage SOP Class, whether references to Structured Report instances are supported, and if not supported, any notifications or lack of notifications to the user that the context information is not displayed. + + +
+
+
+ + Non-Patient Object Storage Service Class +
+ Overview +
+ Scope + The Non-Patient Object Storage Service Class defines an application-level class-of-service that allows one DICOM AE to send a SOP Instance of a non-patient-related information object to another DICOM AE. +
+
+ Service Definition + The Non-Patient Object Storage Service Class includes several SOP Classes, each using an IOD defined in (see ). The Non-Patient Object Storage Service Class uses the C-STORE DIMSE Service specified in . A successful completion of the C-STORE has the following semantics: + + + Both the SCU and the SCP support the type of information to be stored. + + + The transferred information is stored in some medium. + + + For some time frame, the stored information may be accessed. + + + + + + Support for the Non-Patient Object Storage Service Class does not imply support for any related Query/Retrieve Service Classes. + + + The duration of the storage is also implementation dependent, but is described in the Conformance Statement of the SCP. + + + The Non-Patient Object Storage Service Class is intended to be used in a variety of environments: e.g., for workstations to transfer SOP Instances to other workstations or archives, for archives to transfer SOP Instances to workstations, etc. + + + +
+
+
+ Association Negotiation + The Association negotiation rules as defined in apply to the SOP Classes of this Service Class. No SOP Class specific application information (extended negotiation) is used. +
+
+ SOP Classes + The application-level services addressed by the Non-Patient Object Storage Service Class definition are specified in the SOP Classes specified in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Standard SOP Classes
+ + SOP Class Name + + + + SOP Class UID + + + + IOD Specification (defined in ) + +
+ Hanging Protocol Storage + + 1.2.840.10008.5.1.4.38.1 + + + + +
+ Color Palette Storage + + 1.2.840.10008.5.1.4.39.1 + + + + +
+ Generic Implant Template Storage + + 1.2.840.10008.5.1.4.43.1 + + + + +
+ Implant Assembly Template Storage + + 1.2.840.10008.5.1.4.44.1 + + + + +
+ Implant Template Group Storage + + 1.2.840.10008.5.1.4.45.1 + + + + +
+ CT Defined Procedure Protocol Storage + + 1.2.840.10008.5.1.4.1.1.200.1 + + + + +
+ Protocol Approval Storage + + 1.2.840.10008.5.1.4.1.1.200.3 + + + + +
+
+
+ Behavior + This Section defines the SCU and SCP behavior for the Non-Patient Object Storage Service. The C-STORE DIMSE-C Service shall be the mechanism used to transfer SOP Instances between peer DICOM AEs as described in . + In addition to the behaviors specified in this section, there may be SOP Class specific behavior requirements, as described in . +
+ Service Class User + A DICOM AE that claims conformance to any of the Non-Patient Object Storage SOP Classes as an SCU shall be capable of sending a SOP Instance that meets the requirements of the related IOD. The Service shall be invoked by the SCU through the use of the DIMSE C-STORE request used in conjunction with the SOP Class. + The SCU shall recognize the status of the C-STORE service and take appropriate action based on the success or failure of the service. The Non-Patient Object Storage Service places no further requirements on what the SCU shall do other than that it shall distinguish between successful and failed C-STORE responses. This behavior shall be documented as part of the Conformance Statement. +
+
+ Service Class Provider + A DICOM AE that claims conformance to any of the Non-Patient Object Storage SOP Classes as an SCP shall receive and store a SOP Instance through the use of the DIMSE C-STORE service used in conjunction with the specific SOP Class. + The SCP shall store and provide access to all Type 1, Type 2, and Type 3 Attributes defined in the IOD, as well as any Standard Extended Attributes (including Private Attributes) included in the SOP Instance. The SCP may, but is not required to validate that the Attributes of the SOP Instance meet the requirements of the associated IOD. + The SCP shall not modify the values of any Attributes in the SOP Instance without assigning a new SOP Instance UID, except that the SCP may modify values of, or add, Type 3 and Private Attributes that do not change the semantics or interpretation of the SOP Instance. + + E.g., an SCP may add values to Alternate Content Description Sequence (0070,0087), to provide an additional description in another language. + + The SCP shall return, via the C-STORE response primitive, the Response Status Code applicable to the associated request. By performing this service successfully, the SCP indicates that the SOP Instance has been successfully stored. shows the response status values. General status code values and fields related to status code values are defined in . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
C-STORE Response Status Values
+ + Service Status + + + + Further Meaning + + + + Status Codes + + + + Related Fields + +
+ Failure + + Refused: Out of Resources + + A700 + + (0000,0902) +
+ Error: Data Set Does Not Match SOP Class + + A900 + + (0000,0901) + (0000,0902) +
+ Error: Cannot Understand + + C000 + + (0000,0901) + (0000,0902) +
+ Success + + + 0000 + + None +
+ + Status Codes are returned in DIMSE response messages (see ). The code values stated in column "Status Codes" are returned in Status Command Element (0000,0900). + +
+
+
+ Conformance Statement Requirements + An implementation may conform to any of the Non-Patient Object Storage SOP Classes as an SCU, SCP or both. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance Requirements + An implementation that conforms to a SOP Class of the Non-Patient Object Storage Service as an SCU shall state in its Conformance Statement: + + + Whether the implementation is a SOP Instance creator for the SOP Class. + + There may be SOP Class specific Conformance Statement requirements for creators of SOP Instances. See . + + + + The behavior of the SCU in the case of a success C-STORE response status. + + + The behavior of the SCU in each case of a failure C-STORE response status. + + +
+
+ SCP Conformance Requirements + An implementation that conforms to a SOP Class of the Non-Patient Object Storage Service as an SCP shall state in its Conformance Statement: + + + The behavior of the SCP in the case of a successful C-STORE operation, including the access method for a stored SOP Instance, and the duration of the storage. + + + The meaning of each case of a failure C-STORE response status, as well as appropriate recovery action. + + + + There may be SOP Class specific Conformance Statement requirements for applications that interpret the SOP Instances for display or further processing. See . + +
+
+
+ Application Behavior for Standard SOP Classes + This section specifies SOP Class specific behaviors for conformant applications. +
+ Hanging Protocol SOP Class +
+ Instance Creator + An implementation that conforms to the Hanging Protocol Storage SOP Class as an SCU and is a SOP Instance creator shall state in its Conformance Statement: + + + The manner in which the values of the Hanging Protocol IOD Attributes are derived from displayed images, layouts, operator intervention or defaults. + + + Any Private Attributes that are used as the value of Selector Attribute (0072,0026) in the Image Set Selector Sequence, Filter Operations Sequence or Sorting Operations Sequence. + + + The optional Attributes that may be included in a Hanging Protocol SOP Instance. + + +
+
+ Display Application + An implementation that conforms to the Hanging Protocol Storage SOP Class as an SCP and interprets the contents of instances of the SOP Class to control the display of images, shall apply all mandatory Hanging Protocol and presentation intent Attributes to the sets of displayed images. Such an implementation shall state in its Conformance Statement: + + + The range of display environments that the application will support (e.g., number of screens, size of screens, overlapping image boxes). + + + The optional Attributes of the Hanging Protocol IOD that it is capable of interpreting and those that are not supported. + + + Description of application behavior when the value of Partial Data Display Handling (0072,0208) is ADAPT_LAYOUT or zero length. + + + Description of application behavior when the display environment of the Hanging Protocol Instance differs from the display environment of the application, with respect to preserving layout versus spatial resolution. + + + The Image Storage SOP Classes for which the Hanging Protocol Storage SOP Class is supported for display control. + + +
+
+
+ Color Palette Storage SOP Class +
+ Instance Creator + An implementation that conforms to the Color Palette Storage SOP Class as an SCU and is a SOP Instance creator shall state in its Conformance Statement: + + + The optional Attributes that may be included in a Color Palette SOP Instance. + + +
+
+ Display Application + An implementation that conforms to the Color Palette Storage SOP Class as an SCP and interprets the contents of instances of the SOP Class to affect the display of images, shall apply all mandatory Color Palette and presentation intent Attributes to the applicable displayed images. + An implementation that conforms to the Color Palette Storage SOP Class as an SCP and interprets the contents of instances of the SOP Class to affect the display of images shall state in its Conformance Statement: + + + The optional Attributes of the Color Palette IOD that it is capable of interpreting and those that are not supported. + + + The Image Storage SOP Classes for which application of the Color Palette Storage SOP Class is supported + + +
+
+
+ Template Storage SOP Classes + An implementation that is a Generic Implant Template Storage, Implant Assembly Template Storage, or Implant Template Group Storage SOP Class SCU may modify information in a SOP Instance that it has previously sent or received. When this SOP Instance is modified and sent to an SCP, it shall be assigned a new SOP Instance UID if there is addition, removal or update of any Attribute within: + + + Generic Implant Template Description Module + + + Generic Implant Template 2D Drawings Module + + + Generic Implant Template 3D Models Module + + + Generic Implant Template Mating Features Module + + + Generic Implant Template Planning Landmarks Module + + + Implant Assembly Template Module + + + Implant Template Group Module + + + Surface Mesh Module + + + Referential integrity between sets of related SOP instances shall be maintained. +
+
+ CT Defined Procedure Protocol Storage SOP Class + An implementation that conforms to the CT Defined Procedure Protocol Storage SOP Class as an SCP shall not modify constraints for which the value of the Modifiable Constraint Flag (0082,0038) is NO. + Modifying protocol constraints changes the semantics of a CT Defined Procedure Protocol Storage SOP Instance. +
+
+ Protocol Approval Storage SOP Class + Approvals are based on assertions. Receipt or generation of an assertion will interact with organizational authentication and authorization policies. For example, an approval may be received by mistake as part of the transfer of a patient record. +
+
+
+ + + Defined Procedure Protocol Query/Retrieve Service Classes +
+ Overview +
+ Scope + The Defined Procedure Protocol Query/Retrieve Service Classes define application-level classes-of-service that facilitate access to Defined Procedure Protocol composite objects. +
+
+ Conventions + Key Attributes serve two purposes; they may be used as Matching Key Attributes or as Return Key Attributes. Matching Key Attributes may be used for matching (criteria to be used in the C-FIND request to determine whether an entity matches the query). Return Key Attributes may be used to specify desired return Attributes (what elements in addition to the Matching Key Attributes have to be returned in the C-FIND response). + + Matching Keys are typically used in an SQL 'WHERE' clause. Return Keys are typically used in an SQL 'SELECT' clause to convey the Attribute values. + + Matching Key Attributes may be of Type "required" (R) or "optional" (O). Return Key Attributes may be of Type 1, 1C, 2, 2C, 3 as defined in . +
+
+ Query/Retrieve Information Model + In order to serve as an SCP of the Defined Procedure Protocol Query/Retrieve Service Class, a DICOM AE possesses information about the Attributes of a number of Defined Procedure Protocol composite SOP Instances. The information is organized into an Information Model. The Information Models for the different SOP Classes specified in this Annex are defined in . +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of a Defined Procedure Protocol Query/Retrieve Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Defined Procedure Protocol Query/Retrieve Service Classes are implemented using the DIMSE-C C-FIND, C-MOVE and C-GET services as defined in . + An SCP of this SOP Class shall support Level-2 conformance as defined in . + The semantics of the C-FIND service are the same as those defined in the Service Definition of the Basic Worklist Management Service Class. + The semantics of the C-MOVE service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. + The semantics of the C-GET service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. +
+
+
+ Defined Procedure Protocol Information Models Definitions + The Defined Procedure Protocol Information Models are identified by the SOP Class negotiated at Association establishment time. Each SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + The Defined Procedure Protocol Information Models are defined in , with the Entity-Relationship Model Definition and Key Attributes Definition analogous to those defined in the Worklist Information Model Definition of the Basic Worklist Management Service. +
+
+ Defined Procedure Protocol Information Models + The Defined Procedure Protocol Information Models are based upon a one level entity: + + + Defined Procedure Protocol object instance. + + + The Defined Procedure Protocol object instance contains Attributes associated with the Procedure Protocol IE of the Composite IODs as defined in . +
+
+ DIMSE-C Service Groups +
+ C-FIND Operation + See the C-FIND Operation definition for the , and substitute "Defined Procedure Protocol" for "Worklist". The "Worklist" Search Method shall be used. + The SOP Class UID identifies the Defined Procedure Protocol Information Model against which the C-FIND is to be performed. The Key Attributes and values allowable for the query are defined in the SOP Class definitions for the Defined Procedure Protocol Information Model. +
+ Service Class User Behavior + No SOP Class specific SCU behavior is defined. +
+
+ Service Class Provider Behavior + No SOP Class specific SCP behavior is defined. +
+
+
+ C-MOVE Operation + See the C-MOVE Operation definition for the . No Extended Behavior or Relational-Retrieve is defined for the Defined Procedure Protocol Query/Retrieve Service Classes. + Query/Retrieve Level (0008,0052) is not relevant to the Defined Procedure Protocol Query/Retrieve Service Classes, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+ C-GET Operation + See the C-GET Operation definition for the . No Extended Behavior or Relational-Retrieve is defined for the Defined Procedure Protocol Query/Retrieve Service Classes. + + More than one entity may be retrieved, using List of UID matching. + +
+
+
+ Association Negotiation + See the Association Negotiation definition for the . +
+
+ SOP Class Definitions +
+ Defined Procedure Protocol Information Model +
+ E/R Models + The Defined Procedure Protocol Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Defined Procedure Protocol Instance. + +
+ Defined Procedure Protocol Information Model E/R Diagram + + + + + +
+
+
+
+ Defined Procedure Protocol Attributes + + defines the Attributes of the Defined Procedure Protocol Information Model. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Defined Procedure Protocol Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + +
+ + Protocol Context + +
+ Custodial Organization Sequence + + (0040,A07C) + + R + + 2 + +
+ >Institution Name + + (0008,0080) + + R + + 2 + +
+ >Institution Code Sequence + + (0008,0082) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >>Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ Responsible Group Code Sequence + + (0008,0220) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Protocol Name + + (0018,1030) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Potential Scheduled Protocol Code Sequence + + (0018,9906) + + R + + 1 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Potential Requested Procedure Code Sequence + + (0018,9907) + + R + + 1 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Potential Reasons for Procedure + + (0018,9908) + + - + + 2 + +
+ Potential Reasons for Procedure Code Sequence + + (0018,9909) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Potential Diagnostic Tasks + + (0018,990A) + + - + + 2 + +
+ Predecessor Protocol Sequence + + (0018,990E) + + R + + 2 + +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Content Creator's Name + + (0070,0084) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Instance Creation Date + + (0008,0012) + + R + + 1 + + Shall be retrieved with Single Value or Range Matching. + See Instance Creation Time for further details. +
+ Instance Creation Time + + (0008,0013) + + R + + 1 + + Shall be retrieved with Single Value or Range Matching. + If both Instance Creation Date and Instance Creation Time are specified for Range Matching, they are to be treated as as if they were a single DateTime Attribute e.g.,the date range July 5 to July 7 and the time range 10am to 6pm specifies the time period starting on July 5, 10am until July 7, 6pm. +
+ + Clinical Trial Context + +
+ Clinical Trial Sponsor Name + + (0012,0010) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ Clinical Trial Protocol ID + + (0012,0020) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ + Equipment Specification + +
+ Equipment Modality + + (0008,0221) + + R + + 1 + +
+ Model Specification Sequence + + (0018,9912) + + R + + 2 + +
+ >Manufacturer + + (0008,0070) + + R + + 1 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ >Manufacturer's Related Model Group + + (0008,0222) + + R + + 2 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ >Manufacturer's Model Name + + (0008,1090) + + R + + 2 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ >Software Versions + + (0018,1020) + + R + + 2 + + Shall be retrieved with Single Value, Wild Card, or Universal Matching. +
+ >Device Serial Number + + (0018,1000) + + - + + 2 + +
+ + Patient Positioning + +
+ Anatomic Region Sequence + + (0008,2218) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+ Primary Anatomic Structure Sequence + + (0008,2228) + + R + + 2 + + This Attribute shall be retrieved with Sequence or Universal matching. +
+ >Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >Code Meaning + + (0008,0104) + + - + + 1 + +
+
+
+ Conformance Requirements + An implementation may conform to one or more of the Defined Procedure Protocol Query/Retrieve SOP Classes as an SCU or SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to the Defined Procedure Protocol Information Model - FIND SOP Class shall support queries against the Defined Procedure Protocol Information Model using the C-FIND SCU Behavior described for the Basic Worklist Management Service Class (see and ). + An implementation that conforms to the Defined Procedure Protocol Information Model - FIND SOP Class as an SCU shall state in its Conformance Statement whether it requests Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to the Defined Procedure Protocol Information Model - FIND SOP Class as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + An implementation that conforms to the Defined Procedure Protocol Information Model - MOVE SOP Class as an SCU shall support transfers against the Defined Procedure Protocol Information Model, using the C-MOVE SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+ C-GET SCU Conformance + An implementation that conforms to the Defined Procedure Protocol Information Model - GET SOP Class as an SCU shall support transfers against the Defined Procedure Protocol Information Model, using the C-GET SCU baseline behavior described for the Query/Retrieve Service Class (see ). +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to the Defined Procedure Protocol Information Model - FIND SOP Class as an SCP shall support queries against the Defined Procedure Protocol Information Model, using the C-FIND SCP Behavior described for the Basic Worklist Management Service Class (see ). + + The contents of the Model Specification Sequence (0018,9912) would be useful to index for systems that support query or selection of appropriate Protocols for specific systems. + + An implementation that conforms to the Defined Procedure Protocol Information Model - FIND SOP Class as an SCP shall state in its Conformance Statement whether it supports Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to the Defined Procedure Protocol Information Model - FIND SOP Class as an SCP shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching and encoding responses. +
+
+ C-MOVE SCP Conformance + An implementation that conforms to the Defined Procedure Protocol Information Model - MOVE SOP Class as an SCP shall support transfers against the Defined Procedure Protocol Information Model, using the C-MOVE SCP baseline behavior described for the Query/Retrieve Service Class (see ). + + It is expected that a device that does not match the contents of the Model Specification Sequence (0018,9912) will not execute the Protocol. + + An implementation that conforms to the Defined Procedure Protocol Information Model - MOVE SOP Class as an SCP, which generates transfers using the C-MOVE operation, shall state in its Conformance Statement appropriate Storage Service Class, under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to the Defined Procedure Protocol Information Model - GET SOP Class as an SCP shall support retrievals against the Defined Procedure Protocol Information Model using the C-GET SCP baseline behavior described for the Query/Retrieve Service Class in . +
+
+
+
+ SOP Classes + The SOP Classes of the Defined Procedure Protocol Query/Retrieve Service Class identify the Information Models, and the DIMSE-C operations supported. + + + + + + + + + + + + + + + + + + + + + + +
Defined Procedure Protocol SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Defined Procedure Protocol Information Model - FIND + + 1.2.840.10008.5.1.4.20.1 +
+ Defined Procedure Protocol Information Model - MOVE + + 1.2.840.10008.5.1.4.20.2 +
+ Defined Procedure Protocol Information Model - GET + + 1.2.840.10008.5.1.4.20.3 +
+
+
+
+
+ + Protocol Approval Query/retrieve Service Classes +
+ Overview +
+ Scope + The Protocol Approval Query/Retrieve Service Classes define application-level classes-of-service that facilitate access to Protocol Approval composite objects. +
+
+ Conventions + Key Attributes serve two purposes; they may be used as Matching Key Attributes or as Return Key Attributes. Matching Key Attributes may be used for matching (criteria to be used in the C-FIND request to determine whether an entity matches the query). Return Key Attributes may be used to specify desired return Attributes (what elements in addition to the Matching Key Attributes have to be returned in the C-FIND response). + + Matching Keys are typically used in an SQL 'where' clause. Return Keys are typically used in an SQL 'select' clause to convey the Attribute values. + + Matching Key Attributes may be of Type "required" (R) or "optional" (O). Return Key Attributes may be of Type 1, 1C, 2, 2C, 3 as defined in . +
+
+ Query/Retrieve Information Model + In order to serve as an SCP of the Protocol Approval Query/Retrieve Service Class, a DICOM AE possesses information about the Attributes of a number of Protocol Approval composite SOP Instances. The information is organized into an Information Model. The Information Models for the different SOP Classes specified in this Annex are defined in . +
+
+ Service Definition + Two peer DICOM AEs implement a SOP Class of a Protocol Approval Query/Retrieve Service Class with one serving in the SCU role and one serving in the SCP role. SOP Classes of the Protocol Approval Query/Retrieve Service Classes are implemented using the DIMSE-C C-FIND, C-MOVE and C-GET services as defined in . + An SCP of this SOP Class shall support Level-2 conformance as defined in . + The semantics of the C-FIND service are the same as those defined in the Service Definition of the Basic Worklist Management Service Class. + The semantics of the C-MOVE service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. + The semantics of the C-GET service are the same as those defined in the Service Definition of the Query/Retrieve Service Class, with the exception that there is only one level of retrieval. +
+
+
+ Protocol Approval Information Models Definitions + The Protocol Approval Information Models are identified by the SOP Class negotiated at Association establishment time. Each SOP Class is composed of both an Information Model and a DIMSE-C Service Group. + The Protocol Approval Information Models are defined in , with the Entity-Relationship Model Definition and Key Attributes Definition analogous to those defined in the Worklist Information Model Definition of the Basic Worklist Management Service. +
+
+ Protocol Approval Information Models + The Protocol Approval Information Models are based upon a one level entity: + + + Protocol Approval object instance. + + + The Protocol Approval object instance contains Attributes associated with the Approval IE of the Composite IODs as defined in . +
+
+ DIMSE-C Service Groups +
+ C-FIND Operation + See the C-FIND Operation definition for the Basic Worklist Management Service Class () , and substitute "Approval" for "Worklist". The "Worklist" Search Method shall be used. + The SOP Class UID identifies the Protocol Approval Information Model against which the C-FIND is to be performed. The Key Attributes and values allowable for the query are defined in the SOP Class definitions for the Protocol Approval Information Model. +
+ Service Class User Behavior + No SOP Class specific SCU behavior is defined. +
+
+ Service Class Provider Behavior + No SOP Class specific SCP behavior is defined. +
+
+
+ C-MOVE Operation + See the C-MOVE Operation definition for the Query/Retrieve Service Class (). No Extended Behavior or Relational-Retrieve is defined for the Protocol Approval Query/Retrieve Service Classes. + Query/Retrieve Level (0008,0052) is not relevant to the Protocol Approval Query/Retrieve Service Classes, and therefore shall not be present in the Identifier. The only Unique Key Attribute of the Identifier shall be SOP Instance UID (0008,0018). The SCU shall supply one UID or a list of UIDs. + + More than one entity may be retrieved, using List of UID matching. + +
+
+ C-GET Operation + See the C-GET Operation definition for the Query/Retrieve Service Class (). No Extended Behavior or Relational-Retrieve is defined for the Protocol Approval Query/Retrieve Service Classes. + + More than one entity may be retrieved, using List of UID matching. + +
+
+
+ Association Negotiation + See the Association Negotiation definition for the Basic Worklist Management Service Class (). +
+
+ SOP Class Definitions +
+ Protocol Approval Information Model +
+ E/R Models + The Protocol Approval Information Model consists of a single entity. In response to a given C-FIND request, the SCP shall send one C-FIND response per matching Protocol Approval Instance. + +
+ Protocol Approval Information Model E/R Diagram + + + + + +
+
+
+
+ Protocol Approval Attributes + + defines the Attributes of the Protocol Approval Information Model. + + Since protocol approvals are generally relevant only in the context of the protocol instance being approved, many searches will be looking for approvals that list a particular protocol instance in the Approval Subject Sequence (0044,0109). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes for the Protocol Approval Information Model
+ + Description / Module + + + + Tag + + + + Matching Key Type + + + + Return Key Type + + + + Remark / Matching Type + +
+ + SOP Common + +
+ Specific Character Set + + (0008,0005) + + - + + 1C + + This Attribute is required if expanded or replacement character sets are used. See and . +
+ SOP Class UID + + (0008,0016) + + R + + 1 + +
+ SOP Instance UID + + (0008,0018) + + U + + 1 + +
+ Instance Creation Date + + (0008,0012) + + R + + 1 + + Shall be retrieved with Single Value or Range Matching. + See Instance Creation Time for further details. +
+ Instance Creation Time + + (0008,0013) + + R + + 1 + + Shall be retrieved with Single Value or Range Matching. + If both Instance Creation Date and Instance Creation Time are specified for Range Matching, they are to be treated as as if they were a single DateTime Attribute e.g.,the date range July 5 to July 7 and the time range 10am to 6pm specifies the time period starting on July 5, 10am until July 7, 6pm. +
+ + Protocol Approval + +
+ Approval Subject Sequence + + (0044,0109) + + R + + 1 + +
+ >Referenced SOP Class UID + + (0008,1150) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ >Referenced SOP Instance UID + + (0008,1155) + + R + + 1 + + Shall be retrieved with List of UID Matching. +
+ Approval Sequence + + (0044,0100) + + R + + 1 + +
+ >Assertion Code Sequence + + (0044,0101) + + R + + 1 + +
+ >>Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >Assertion UID + + (0044,0102) + + - + + 1 + +
+ >Asserter Identification Sequence + + (0044,0103) + + R + + 1 + +
+ >>Observer Type + + (0040,A084) + + - + + 1 + +
+ >>Person Name + + (0040,A123) + + R + + 1 + +
+ >>Person Idenfication Code Sequence + + (0040,1101) + + R + + 1 + +
+ >>>Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>>Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >>Organizational Role Code Sequence + + (0044,010A) + + R + + 2 + +
+ >>>Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>>Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >>Station Name + + (0008,1010) + + - + + 3 + +
+ >>Device UID + + (0018,1002) + + - + + 3 + +
+ >>Manufacturer + + (0008,0070) + + - + + 3 + +
+ >>Manufacturer's Model Name + + (0008,1090) + + - + + 3 + +
+ >>Station AE Title + + (0008,0055) + + - + + 3 + +
+ >>Institution Name + + (0008,0080) + + R + + 1 + +
+ >>Institution Code Sequence + + (0008,0082) + + R + + 1 + +
+ >>>Code Value + + (0008,0100) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>>Coding Scheme Designator + + (0008,0102) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Universal matching. +
+ >>>Code Meaning + + (0008,0104) + + - + + 1 + +
+ >>Institutional Department Name + + (0008,1040) + + U + + 2 + +
+ >Assertion DateTime + + (0044,0104) + + R + + 1 + + This Attribute shall be retrieved with Single Value or Range Matching. +
+ >Assertion Expiration DateTime + + (0044,0105) + + R + + 2 + + This Attribute shall be retrieved with Single Value or Range Matching. +
+ >Assertion Comments + + (0044,0106) + + - + + 2 + +
+ >Related Assertion Sequence + + (0044,0107) + + U + + 1 + +
+ >>Referenced Assertion UID + + (0044,0108) + + U + + 1 + +
+ + Enhanced General Equipment + +
+ Manufacturer + + (0008,0070) + + - + + 1 + +
+ Manufacturer's Model Name + + (0008,1090) + + - + + 2 + +
+ Software Versions + + (0018,1020) + + - + + 2 + +
+ + The Enhanced General Equipment Module describes the equipment that created the Protocol Approval instance, not the equipment on which a referenced Protocol will be performed. + +
+
+ Conformance Requirements + An implementation may conform to one or more of the Protocol Approval Query/Retrieve SOP Classes as an SCU or SCP. The Conformance Statement shall be in the format defined in . +
+ SCU Conformance +
+ C-FIND SCU Conformance + An implementation that conforms to the Protocol Approval Information Model - FIND SOP Class shall support queries against the Protocol Approval Information Model using the C-FIND SCU Behavior described for the Basic Worklist Management Service Class (see and ). + An implementation that conforms to the Protocol Approval Information Model - FIND SOP Class as an SCU shall state in its Conformance Statement whether it requests Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + An implementation that conforms to the Protocol Approval Information Model - FIND SOP Class as an SCU shall state in its Conformance Statement how it makes use of Specific Character Set (0008,0005) when encoding queries and interpreting responses. +
+
+ C-MOVE SCU Conformance + + An implementation that conforms to the Protocol Approval Information Model - MOVE SOP Class as an SCU shall support transfers against the Protocol Approval Information Model, using the C-MOVE SCU baseline behavior described for the Query/Retrieve Service Class (see and ). +
+
+ C-GET SCU Conformance + An implementation that conforms to the Protocol Approval Information Model - GET SOP Class as an SCU shall support transfers against the Protocol Approval Information Model, using the C-GET SCU baseline behavior described for the Query/Retrieve Service Class (see ). +
+
+
+ SCP Conformance +
+ C-FIND SCP Conformance + An implementation that conforms to the Protocol Approval Information Model - FIND SOP Class as an SCP shall support queries against the Protocol Approval Information Model, using the C-FIND SCP Behavior described for the Basic Worklist Management Service Class (see ). + + The contents of the Referenced SOP Instance UID (0008,1155) in the Approval Subject Sequence (0044,0109) would be useful to index since querying for approvals of a specific Protocol instance will be very common. + + An implementation that conforms to the Protocol Approval Information Model - FIND SOP Class as an SCP shall state in its Conformance Statement: + + + whether it supports Type 3 Return Key Attributes, and shall list these Optional Return Key Attributes. + + + how it makes use of Specific Character Set (0008,0005) when interpreting queries, performing matching and encoding responses. + + + any behaviors that involve not returning matching instances (e.g. not returning an older approval instance that has been superceded/overridden by a newer approval instance). + + +
+
+ C-MOVE SCP Conformance + An implementation that conforms to the Protocol Approval Information Model - MOVE SOP Class as an SCP shall support transfers against the Protocol Approval Information Model, using the C-MOVE SCP baseline behavior described for the Query/Retrieve Service Class (see ). + An implementation that conforms to the Protocol Approval Information Model - MOVE SOP Class as an SCP, which generates transfers using the C-MOVE operation, shall state in its Conformance Statement appropriate Storage Service Class, under which it shall support the C-STORE sub-operations generated by the C-MOVE. +
+
+ C-GET SCP Conformance + An implementation that conforms to the Protocol Approval Information Model - GET SOP Class as an SCP shall support retrievals against the Protocol Approval Information Model using the C-GET SCP baseline behavior described for the Query/Retrieve Service Class in . +
+
+
+
+ SOP Classes + The SOP Classes of the Protocol Approval Query/Retrieve Service Class identify the Information Models, and the DIMSE-C operations supported. + + + + + + + + + + + + + + + + + + + + + + +
Protocol Approval SOP Classes
+ + SOP Class Name + + + + SOP Class UID + +
+ Protocol Approval Information Model - FIND + + 1.2.840.10008.5.1.4.1.1.200.4 +
+ Protocol Approval Information Model - MOVE + + 1.2.840.10008.5.1.4.1.1.200.5 +
+ Protocol Approval Information Model - GET + + 1.2.840.10008.5.1.4.1.1.200.6 +
+
+
+
+
+ +
diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part15.xml b/Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part15.xml new file mode 100644 index 0000000..5bc4866 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer.Tests/SpecXML/part15.xml @@ -0,0 +1,24293 @@ + + + PS3.15 + DICOM PS3.15 2017c - Security and System Management Profiles + + + DICOM Standards Committee + + + 2017 + NEMA + + + + Notice and Disclaimer + The information in this publication was considered technically sound by the consensus of + persons engaged in the development and approval of the document at the time it was developed. + Consensus does not necessarily mean that there is unanimous agreement among every person + participating in the development of this document. + NEMA standards and guideline publications, of which the document contained herein is one, + are developed through a voluntary consensus standards development process. This process brings + together volunteers and/or seeks out the views of persons who have an interest in the topic + covered by this publication. While NEMA administers the process and establishes rules to + promote fairness in the development of consensus, it does not write the document and it does + not independently test, evaluate, or verify the accuracy or completeness of any information or + the soundness of any judgments contained in its standards and guideline publications. + NEMA disclaims liability for any personal injury, property, or other damages of any nature + whatsoever, whether special, indirect, consequential, or compensatory, directly or indirectly + resulting from the publication, use of, application, or reliance on this document. NEMA + disclaims and makes no guaranty or warranty, expressed or implied, as to the accuracy or + completeness of any information published herein, and disclaims and makes no warranty that the + information in this document will fulfill any of your particular purposes or needs. NEMA does + not undertake to guarantee the performance of any individual manufacturer or seller's products + or services by virtue of this standard or guide. + In publishing and making this document available, NEMA is not undertaking to render + professional or other services for or on behalf of any person or entity, nor is NEMA + undertaking to perform any duty owed by any person or entity to someone else. Anyone using + this document should rely on his or her own independent judgment or, as appropriate, seek the + advice of a competent professional in determining the exercise of reasonable care in any given + circumstances. Information and other standards on the topic covered by this publication may be + available from other sources, which the user may wish to consult for additional views or + information not covered by this publication. + NEMA has no power, nor does it undertake to police or enforce compliance with the contents + of this document. NEMA does not certify, test, or inspect products, designs, or installations + for safety or health purposes. Any certification or other statement of compliance with any + health or safety-related information in this document shall not be attributable to NEMA and is + solely the responsibility of the certifier or maker of the statement. + + + Foreword + This DICOM Standard was developed according to the procedures of the DICOM Standards Committee. + The DICOM Standard is structured as a multi-part document using the guidelines established in . + DICOM® is the registered trademark of the National Electrical Manufacturers Association for its standards publications relating to digital communications of medical information, all rights reserved. + HL7® and CDA® are the registered trademarks of Health Level Seven International, all rights reserved. + SNOMED®, SNOMED Clinical Terms®, SNOMED CT® are the registered trademarks of the International Health Terminology Standards Development Organisation (IHTSDO), all rights reserved. + LOINC® is the registered trademark of Regenstrief Institute, Inc, all rights reserved. + + + Scope and Field of Application + + This part of the DICOM Standard specifies Security and System Management Profiles to which implementations may claim conformance. Security and System Management Profiles are defined by referencing externally developed standard protocols, such as TLS, ISCL, DHCP, and LDAP, with attention to their use in a system that uses DICOM Standard protocols for information interchange. +
+ Security Policies and Mechanisms + + The DICOM standard does not address issues of security policies, though clearly adherence to appropriate security policies is necessary for any level of security. The standard only provides mechanisms that could be used to implement security policies with regard to the interchange of DICOM objects between Application Entities. For example, a security policy may dictate some level of access control. This Standard does not consider access control policies, but does provide the technological means for the Application Entities involved to exchange sufficient information to implement access control policies. + This Standard assumes that the Application Entities involved in a DICOM interchange are implementing appropriate security policies, including, but not limited to access control, audit trails, physical protection, maintaining the confidentiality and integrity of data, and mechanisms to identify users and their rights to access data. Essentially, each Application Entity must insure that their own local environment is secure before even attempting secure communications with other Application Entities. + When Application Entities agree to interchange information via DICOM through association negotiation, they are essentially agreeing to some level of trust in the other Application Entities. Primarily Application Entities trust that their communication partners will maintain the confidentiality and integrity of data under their control. Of course that level of trust may be dictated by local security and access control policies. + Application Entities may not trust the communications channel by which they communicate with other Application Entities. Thus, this Standard provides mechanisms for Application Entities to securely authenticate each other, to detect any tampering with or alteration of messages exchanged, and to protect the confidentiality of those messages while traversing the communications channel. Application Entities can optionally utilize any of these mechanisms, depending on the level of trust they place in the communications channel. + This Standard assumes that Application Entities can securely identify local users of the Application Entity, and that user's roles or licenses. Note that users may be persons, or may be abstract entities, such as organizations or pieces of equipment. When Application Entities agree to an exchange of information via DICOM, they may also exchange information about the users of the Application Entity via the Certificates exchanged in setting up the secure channel. The Application Entity may then consider the information contained in the Certificates about the users, whether local or remote, in implementing an access control policy or in generating audit trails. + This Standard also assumes that Application Entities have means to determine whether or not the "owners" (e.g., patient, institution) of information have authorized particular users, or classes of users to access information. This Standard further assumes that such authorization might be considered in the access control provided by the Application Entity. At this time, this Standard does not consider how such authorization might be communicated between Application Entities, though that may be a topic for consideration at some future date. + This Standard also assumes that an Application Entity using TLS has secure access to or can securely obtain X.509 key Certificates for the users of the application entity. In addition, this standard assumes that an Application Entity has the means to validate an X.509 certificate that it receives. The validation mechanism may use locally administered authorities, publicly available authorities, or some trusted third party. + This Standard assumes that an Application Entity using ISCL has access to an appropriate key management and distribution system (e.g., smartcards). The nature and use of such a key management and distribution system is beyond the scope of DICOM, though it may be part of the security policies used at particular sites. +
+
+ System Management Profiles + + The System Management Profiles specified in this Part are designed to support automation of the configuration management processes necessary to operate a system that uses DICOM Standard protocols for information interchange. + This Part assumes that the Application Entities may operate in a variety of network environments of differing complexity. These environments may range from a few units operating on an isolated network, to a department-level network with some limited centralized network support services, to an enterprise-level network with significant network management services. Note that the System Management Profiles are generally addressed to the implementation, not to Application Entities. The same Profiles need to be supported by the different applications on the network. +
+
+ + Normative References + + The following standards contain provisions that, through reference in this text, constitute provisions of this Standard. At the time of publication, the editions indicated were valid. All standards are subject to revision, and parties to agreements based on this Standard are encouraged to investigate the possibilities of applying the most recent editions of the standards indicated below. + + + ISO/IEC Directives, Part 2 + + ISO/IEC + + 2016/05 + 7.0 + Rules for the structure and drafting of International Standards + + + + + + ANSI X9.52 American National Standards Institute. ANSI X9.52-1998, Triple Data Encryption Algorithm Modes of Operation. 1998. + ECMA 235, The ECMA GSS-API Mechanism + FIPS PUB 46 Data Encryption Standard + FIPS PUB 81 DES Modes of Operation + IETF Internet X.509 Public Key Infrastructure; Time Stamp Protocols; March 2000 + ISO/IEC 10118-:1998 Information technology - Security techniques - Hash-functions - Part 3: Dedicated hash-functions (RIPEMD-160 reference) + Note: The draft RIPEMD-160 specification and sample code are also available at ftp://ftp.esat.kuleuven.ac.be/pub/bosselae/ripemd + ISO 7498-1, Information Processing Systems - Open Systems Interconnection - Basic Reference Model + ISO 7498-2, Information processing systems - Open Systems Interconnection - Basic reference Model - Part 2: Security Architecture + ISO/TR 8509, Information Processing Systems - Open Systems Interconnection - Service Conventions + ISO 8649:1987, Information Processing Systems - Open Systems Interconnection - Service Definition for the Association Control Service Element + Integrated Secure Communication Layer V1.00 MEDIS-DC + ITU-T Recommendation X.509 (03/00) "Information technology - Open Systems Interconnection - The directory: Public-key and attribute certificate frameworks" + + ITU-T Recommendation X.509 is similar to ISO/IEC 9594-8 1990. However, the ITU-T recommendation is the more familiar form, and was revised in 1993 and 2000, with two sets of corrections in 2001. ITU-T was formerly known as CCITT. + + RFC1035 Domain Name System (DNS) + RFC1305 Network Time Protocol (Version 3) Specification, Implementation + RFC2030 Simple Network Time Protocol (SNTP) Version 4 + RFC2131 Dynamic Host Configuration Protocol + RFC2132 Dynamic Host Configuration Protocol Options + RFC2136 Dynamic Updates in the Domain Name System (DNS UPDATE) + RFC2181 Clarifications to the DNS Specification + RFC2219 Use of DNS Aliases for Network Services + RFC2246, Transport Layer Security (TLS) 1.0 Internet Engineering Task Force + + TLS is derived from SSL 3.0, and is largely compatible with it. + + RFC2251 Lightweight Directory Access Protocol (v3) + RFC2313 PKCS #1: RSA Encryption, Version 1.5, March 1998. + RFC2563 DHCP Option to Disable Stateless Auto-Configuration in IPv4 Clients + RFC2782 A DNS RR for specifying the location of services (DNS SRV) + RFC2849 The LDAP Data Interchange Format (LDIF) + RFC2898 PKCS #5: Password-Based Cryptography Specification Version 2.0, September 2000 + RFC3211 Password-based Encryption for CMS, December 2001 + RFC3268 Advanced Encryption Standard (AES) Ciphersuites for Transport Layer Security (TLS), June 2002. + RFC3447 PKCS #1 RSA Cryptography Specifications Version 2.1, February 2003 + + The RSA Encryption Standard is also defined in informative annex A of ISO/IEC 9796, and in Normative of the CEN/TC251 European Prestandard prENV 12388:1996. + + RFC3852 Cryptographic Message Syntax,July 2004 + RFC3370 Cryptographic Message Syntax (CMS) Algorithms, August 2002 + RFC3565 Use of the Advanced Encryption Standard (AES) Encryption Algorithm in Cryptographic Message Syntax (CMS), July 2003 + SHA-1 National Institute of Standards and Technology, FIPS Pub 180-1: Secure Hash Standard, 17 April 1995 + SHA-2 National Institute of Standards and Technology, FIPS Pub 180-2: Secure Hash Standard, 1 August 2002 + RFC3851 Secure/Multipurpose Internet Mail Extensions (S/MIME) Version 3.1 Message Specification + RFC3853 S/MIME Advanced Encryption Standard (AES) Requirement for the Session Initiation Protocol (SIP) + RFC5424 The Syslog Protocol + RFC5425 Transport Layer Security (TLS) Transport Mapping for Syslog + RFC5426 Transmission of Syslog Messages over UDP + + Normative RFC's are frequently updated by issuance of subsequent RFC's. The original older RFC is not modified to include references to the newer RFC. + + + + Definitions + + For the purposes of this Standard the following definitions apply. +
+ Reference Model Definitions + + This part of the Standard makes use of the following terms defined in ISO 7498-1: + + +Application Entity + + +Protocol Data Unit or Layer Protocol Data Unit + + +Transport Connection + + +
+
+ Reference Model Security Architecture Definitions + + This Part of the Standard makes use of the following terms defined in ISO 7498-2: + + + Data Confidentiality + + The definition is "the property that information is not made available or disclosed to unauthorized individuals, entities or processes." + + + + Data Origin Authentication + + The definition is "the corroboration that the source of data received is as claimed." + + + + Data Integrity + + The definition is "the property that data has not been altered or destroyed in an unauthorized manner." + + + + Key Management + + The definition is "the generation, storage, distribution, deletion, archiving and application of keys in accordance with a security policy." + + + + Digital Signature + + The definition is "Data appended to, or a cryptographic transformation of, a data unit that allows a recipient of the data unit to prove the source and integrity of that unit and protect against forgery e.g., by the recipient." + + + +
+
+ ACSE Service Definitions + + This part of the Standard makes use of the following terms defined in ISO 8649: + + +Association or Application Association + + +
+
+ Security Definitions + + This Part of the Standard makes use of the following terms defined in ECMA 235: + + + Security Context + + The definition is "security information that represents, or will represent a Security Association to an initiator or acceptor that has formed, or is attempting to form such an association." + + + +
+
+ DICOM Introduction and Overview Definitions + + This Part of the Standard makes use of the following terms defined in : + + +Attribute + + +
+
+ DICOM Conformance Definitions + + This Part of the Standard makes use of the following terms defined in : + + +Security Profile + + +
+
+ DICOM Information Object Definitions + + This Part of the Standard makes use of the following terms defined in : + + +Module + + +
+
+ DICOM Service Class Definitions + + This Part of the Standard makes use of the following terms defined in : + + +Service Class + + +Service-Object Pair (SOP) Instance + + +
+
+ DICOM Communication Support Definitions + + This Part of the Standard makes use of the following terms defined in : + + +DICOM Upper Layer + + +
+
+ DICOM Security Profile Definitions + + The following definitions are commonly used in this Part of the DICOM Standard: + + +Secure Transport Connection: + +a Transport Connection that provides some level of protection against tampering, eavesdropping, masquerading. + + + +Message Authentication Code: + +A digest or hash code derived from a subset of Data Elements. + + + +Certificate: + +An electronic document that identifies a party and that party's public encryption algorithm, parameters, and key. The Certificate also includes, among other things, the identity and a digital signature from the entity that created the certificate. The content and format of a Certificate are defined by ITU-T Recommendation X.509. + + + +
+
+ + Symbols and Abbreviations + + The following symbols and abbreviations are used in this Part of the Standard. + + +ACR + +American College of Radiology + + + +AE + +Application Entity + + + +AES + +Advanced Encryption Standard + + + +ANSI + +American National Standards Institute + + + +CEN TC251 + +Comite European de Normalisation-Technical Committee 251-Medical Informatics + + + +CBC + +Cipher Block Chaining + + + +CCIR + +Consultative Committee, International Radio + + + +CN + +Common Name + + + +DES + +Data Encryption Standard + + + +DHCP + +Dynamic Host Configuration Protocol + + + +DICOM + +Digital Imaging and Communications in Medicine + + + +DN + +Distinguished Name + + + +DNS + +Domain Name System + + + +DDNS + +Dynamic Domain Name System + + + +ECMA + +European Computer Manufacturers Association + + + +EDE + +Encrypt-Decrypt-Encrypt + + + +HL7 + +Health Level 7 + + + +IEC + +International Electrical Commission + + + +IEEE + +Institute of Electrical and Electronics Engineers + + + +IETF + +Internet Engineering Task Force + + + +IOD + +Information Object Definition + + + +ISCL + +Integrated Secure Communication Layer + + + +ISO + +International Standards Organization + + + +JIRA + +Japan Medical Imaging and Radiological Systems Industries Association + + + +LDAP + +Lightweight Directory Access Protocol + + + +LDIF + +LDAP Interchange Format + + + +MAC + +Message Authentication Code + + + +MD-5 + +Message Digest - 5 + + + +MEDIS-DC + +Medical Information System Development Center + + + +MTU + +Maximum Transmission Unit + + + +NEMA + +National Electrical Manufacturers Association + + + +NTP + +Network Time Protocol + + + +OID + +Object Identifier (analogous to UID) + + + +PDU + +Protocol Data Unit + + + +RDN + +Relative Distinguished Name + + + +RFC + +Request For Comment (used for standards issued by the IETF) + + + +RR + +Resource Record (when used in the context of DNS) + + + +RSA + +Rivest-Shamir-Adleman + + + +SCP + +Service Class Provider + + + +SCU + +Service Class User + + + +SHA + +Secure Hash Algorithm + + + +SNTP + +Simple Network Time Protocol + + + +SOP + +Service-Object Pair + + + +SSH + +Secure Shell + + + +SSL + +Secure Sockets Layer + + + +TLS + +Transport Layer Security + + + +UID + +Unique Identifier + + + +UTC + +Universal Coordinated Time + + + + + + Conventions + + Terms listed in Section 3 Definitions are capitalized throughout the document. + + + Security and System Management Profile Outlines + + An implementation may claim conformance to any of the Security and System Management Profiles individually. It may also claim conformance to more than one Security or System Management Profile. It shall indicate in its Conformance Statement how it chooses which profiles to use for any given transaction. +
+ Secure Use Profiles + + An implementation may claim conformance to one or more Secure Use Profiles. Such profiles outline the use of attributes and other Security Profiles in a specific fashion. + Secure Use Profiles are specified in . +
+
+ Secure Transport Connection Profiles + + An implementation may claim conformance to one or more Secure Transport Connection Profiles. + A Secure Transport Connection Profile includes the following information: + + +Description of the protocol framework and negotiation mechanisms + + +Description of the entity authentication an implementation shall support + + +The identity of the entities being authenticated + + +The mechanism by which entities are authenticated + + +Any special considerations for audit log support + + + + + + + + Description of the encryption mechanism an implementation shall support + + +The method of distributing session keys + + +The encryption protocol and relevant parameters + + + + + + + +Description of the integrity check mechanism an implementation shall support + + + Secure Transport Connection Profiles are specified in . +
+
+ Digital Signature Profile + + An implementation may claim conformance to one or more Digital Signature Profiles. + A Digital Signature profile consists of the following information: + + +The role that the Digital Signature plays, including: + + +Who or what entity the Digital Signature represents. + + +A description of the purpose of the Digital Signature. + + +The conditions under which the Digital Signature is included in the Data Set. + + + + +A list of Attributes that shall be included in the Digital Signature. + + +The mechanisms that shall be used to generate or verify the Digital Signature, including: + + +The algorithm and relevant parameters that shall be used to create the MAC or hash code, including the Value to be used for the MAC Algorithm (0400,0015) Attribute. + + +The encryption algorithm and relevant parameters that shall be used to encrypt the MAC or hash code in forming the Digital Signature. + + +The certificate type or key distribution mechanism that shall be used, including the Value to be used for the Certificate Type (0400,0110) Attribute. + + +Any requirements for the Certified Timestamp Type (0400,0305) and Certified Timestamp (0400,0310) Attributes. + + + + +Any special requirements for identifying the signatory. + + +The relationship with other Digital Signatures, if any. + + +Any other factors needed to create, verify, or interpret the Digital Signature + + + + Digital Signature Profiles are specified in . +
+
+ Media Storage Security Profiles + + An implementation may claim conformance to one or more Media Storage Application Profiles, which in turn require conformance to one or more Media Storage Security Profiles. + + An implementation may not claim conformance to a Media Storage Security Profile without claiming conformance to a Media Storage Application Profile. + + A Media Storage Security Profile includes the following specifications: + + +What aspects of security are addressed by the profile. + + +The restrictions on the types of DICOM Files that can be secured, if any. + + +How the DICOM Files will be encapsulated and secured. + + + Media Storage Security Profiles are specified in . +
+
+ Network Address Management Profiles + + An implementation may claim conformance to one or more Network Address Management Profiles. Such profiles outline the use of non-DICOM network protocols to obtain the network addresses for the implementation. + Network Address Management Profiles are specified in . +
+
+ Time Synchronization Profiles + + An implementation may claim conformance to one or more Time Synchronization Profiles. Such profiles outline the use of non-DICOM protocols to set the current time for the implementation. + Time Synchronization Profiles are specified in . +
+
+ Application Configuration Management Profiles + + An implementation may claim conformance to one or more Application Configuration Management Profiles. Such profiles outline the use of non-DICOM network protocols to obtain the descriptions, addresses and capabilities of other devices with which the implementation may communicate using the DICOM Protocol. They also specify the use of those non-DICOM protocols for the implementation to publish or announce its description, addresses and capabilities. They also specify how implementation specific configuration information can be obtained by devices. + Application Configuration Management Profiles are specified in . +
+
+ Audit Trail Profiles + + An implementation may claim conformance to one or more Audit Trail Profiles. Such profiles outline the generation and transport of audit messages for security and privacy policy enforcement. + Audit Trail Profiles are specified in . +
+
+ + Configuration Profiles + + Configuration management support is implemented by means of protocols defined in standards other than the DICOM standard. These protocols are described here in terms of actors, transactions, and profiles. + Actors are analogous to the Application Entities used within the DICOM profile. An actor is a collection of hardware and software processes that perform a particular role. When a device provides or uses a service it will include an actor to handle the relevant network activity. DICOM Configuration actors may co-exist with other Application Entities on a device. Some DICOM Configuration actors exist as parts of general use IT equipment. Like the Application Entity, specification of an Actor does not imply anything about the details of the actual implementation. + The actor interactions are defined in terms of Transactions. Each transaction is given a name. The transaction may in turn comprise a variety of activity. All transactions are defined in terms of actors that are communicating. The relationships between actors in a transaction may be more complex than the simple SCU and SCP roles in DICOM activities. When the transaction includes interactions with a person, the transactions may be implemented by user interfaces, removable media. and other mechanisms. The person is described in terms of being an actor from the perspective of the transaction use case model. More typically the transactions are a series of network activities that perform a specific operation. + A transaction includes both mandatory and optional components. An Actor that is implementing a transaction is required to implement all of the mandatory components. + Some transactions include human actors in the transaction definition. These actors are not defined as actors elsewhere, nor are they included in profile descriptions. They exist to specify that some sort of mechanism must be provided to permit these people to interact with the computer actor. Other details of how that user interface is provided are not specified by this standard. For an example, see the definition of the Configure DHCP transaction. + Conformance is further managed by means of Profiles. A Profile is defined in terms of what transactions are required for an actor and what transactions are optional. An implementation of a specific actor is documented by specifying what optional transactions and transaction components have been implemented. An implementation that omits any required transactions or components cannot claim to be an implementation of that Actor. + For example, in the Network Address Management Profile the DHCP Server is required to perform the three Transactions to configure the DHCP server, find and use DHCP servers, and maintain the DHCP leases. It may also support the transaction to update the DNS server by means of DDNS coordination. + A Profile includes definitions for more than one Actor. It specifies the transactions for all of the actors that cooperate to perform a function. For example, the Network Address Management Profile covers the DHCP Server actor, the DHCP client Actor, and the DNS Server actor. There must be at least one DHCP Server and one DHCP Client for the system to be useful. The DNS Server itself is optional because the DHCP Server need not implement the DDNS Coordination transaction. If the DNS Server is part of the system, the DDNS coordination is required and the DHCP Server will be expected to participate in the DDNS Coordination transaction. + + There may be a DNS server present on the same network as a DHCP Server, but if it is not providing the DNS Server actor from this profile it is not part of the DICOM Configuration activities. + + The profiles, actors, and transactions are summarized in the following sections. The detailed description of actor and transactions for each specific profile are described in annexes for each profile. The transactions are documented in terms of parameters and terms from their original standards document, e.g., an RFC for Internet protocols. The full details of the transaction are not described in the annex, only particular details that are relevant to the DICOM application of that transaction. The complete details for these external protocols are documented in the relevant standards documents for the external protocols. Compliance with the requirements of a particular profile shall include compliance with these external protocol documents. +
+ Actors + + + DHCP Server + + + The DHCP Server is a computer/software feature that is provided with a network configuration description, and that provides startup configuration services in accordance with the DHCP protocol. + + DHCP Client + + + The DHCP Client is a software feature that is used to obtain TCP/IP parameters during the startup of a computer. It continues operation to maintain validity of these parameters. + + DNS Server + + The DNS server is a computer/software feature that provides IP related information in response to queries from clients utilizing the DNS protocol. It is a part of a federated database facility that maintains the current database relating machine names to IP address information. The DNS server may also be isolated from the worldwide federated database and provide only local DNS services. + + DNS Client + + The DNS client as a computer/software feature that utilizes the DNS protocols to obtain IP information when given hostnames. The hostnames may be in configuration files or other files instead of explicit IP addresses. The hostnames are converted into IP addresses dynamically when necessary. The DNS client uses a DNS server to provide the necessary information. + + NTP Server + + + The NTP server is a computer/software feature that provides time services in accordance with the NTP or SNTP protocol. + + NTP Client + + + The NTP client is software that obtains time information from an NTP server and maintains the client time in synchronization with the time signals from the NTP server. + + SNTP Client + + + The SNTP client is software that obtains time information from an NTP server and maintains the client time in approximate synchronization with time signals from the NTP server. The SNTP client synchronization is not maintained with the accuracy or precision that NTP provides. + + LDAP Server + + + The LDAP server is a computer/ software feature that maintains an internal database of various directory information. Some of this directory information corresponds to DICOM Configuration schema. The LDAP server provides network access to read and update the directory information. The LDAP server provides a mechanism for external loading, unloading, and backup of directory information. The LDAP server may be part of a federated network of servers that provides a coordinated view of a federated directory database in accordance with the rules of the LDAP protocols. + + LDAP Client + + + The LDAP client utilizes the LDAP protocol to make queries to an LDAP server. The LDAP server maintains a database and responds to these queries based on the contents of this database. +
+
+ Transactions + + The following transactions are used to provide communications between actors in accordance with one or more of the DICOM Configuration protocols. + + Configure DHCP Server + + This transaction changes the configuration on a DHCP server to reflect additions, deletions, and changes to the IP parameters that have been established for this network. + + Find and Use DHCP Server + + This transaction is a sequence of network messages that comply with the rules of the DHCP protocol. It allows a DHCP client to find available DHCP servers and select the server appropriate for that client. This transaction obtains the mandatory IP parameter information from the DHCP server and obtains additional optional parameters from the DHCP server. + + Configure Client + + The service staff uses this transaction to set the initial configuration for a client. + + Maintain Lease + + This transaction deals with how the DHCP client should behave when its IP lease is not renewed. + + DDNS Coordination + + This transaction documents whether the DHCP server is coordinating with a DNS server so that access to the DHCP client can be maintained using the hostname assigned to the DHCP client. + + Resolve Hostname + + + This transaction obtains the IP address for a computer when given a hostname. + + Maintain Time + + These transactions are the activities needed for an NTP or SNTP client to maintain time synchronization with a master time service. + + Find NTP Server + + This transaction is the autodiscovery procedure defined for NTP. This may use either a broadcast method or a DHCP supported method. + + Find LDAP Server + + In this transaction the DNS server is queried to obtain the IP address, port, and name of the LDAP server. + + Query LDAP Server + + + In this transaction the LDAP server is queried regarding contents of the LDAP database. + + Client Update LDAP Server + + This transaction updates the configuration database using LDAP update instructions from the client being configured. + + Maintain LDAP Server + + This transaction updates the configuration database using local services of the LDAP server. + + shows the actors and their transactions. The usual device will have an NTP Client, DHCP Client, and LDAP client in addition to the other applications actors. The transactions "Configure DHCP Server", "Configure Client", and "Maintain LDAP Server" are not shown because these transactions are between a software actor and a human actor. DICOM does not specify the means or user interface. It only requires that certain capabilities be supported. + +
+ Transactions and Actors + + + + + + +
+
+
+
+ + Secure Use Profiles (Normative) + +
+ Online Electronic Storage Secure Use Profile + + The Online Electronic Storage Secure Use Profile allows Application Entities to track and verify the status of SOP Instances in those cases where local security policies require tracking of the original data set and subsequent copies. + The Conformance Statement shall indicate in what manner the system restricts remote access. +
+ SOP Instance Status + + An implementation that conforms to the Online Electronic Storage Secure Use Profile shall conform to the following rules regarding the use of the SOP Instance Status (0100,0410) Attribute with SOP Instances that are transferred using the Storage Service Class: + + + An Application Entity that supports the Online Electronic Storage Secure Use Profile and that creates a SOP Instance intended for diagnostic use in Online Electronic Storage shall: + + +Set the SOP Instance Status to Original (OR). + + + Include the following Attributes: + + +the SOP Class UID (0008,0016) and SOP Instance UID (0008,0018) + + +the Instance Creation Date (0008,0012) and Instance Creation Time (0008,0013), if known + + +the SOP Instance Status + + +the SOP Authorization Date and Time (0100,0420) + + +the SOP Authorization Comment, if any (0100,0424) + + +the SOP Equipment Certification Number (0100,0426) + + +the Study Instance UID (0020,000D) and Series Instance UID (0020,000E) + + +any Attributes of the General Equipment Module that are known + + +any overlay data present + + +any image data present + + + + + + + The Application Entity that holds a SOP Instance where the SOP Instance Status is Original (OR) may change the SOP Instance Status to Authorized Original(AO) as long as the following rules are followed: + + +The Application Entity shall determine that an authorized entity has certified the SOP Instance as useable for diagnostic purposes. + + +The Application Entity shall change the SOP Instance Status to Authorized Original (AO). The SOP Instance UID shall not change. + + +The Application Entity shall set the SOP Authorization Date and Time (0100,0420) and Authorization Equipment Certification Number (0100,0426) Attributes to appropriate values. It may also add an appropriate SOP Authorization Comment (0100,0424) Attribute. + + + + + There shall only be one Application Entity that holds a SOP Instance where the SOP Instance Status is Original (OR) or Authorized Original (AO). The Application Entity that holds such a SOP instance shall not delete it. + + + When communicating with an Application Entity that supports Online Electronic Storage the Application Entity that holds a SOP Instance where the SOP Instance Status is Original(OR) or Authorized Original(AO) may transfer that SOP Instance to another Application Entity that also conforms to the Online Electronic Storage Secure Use Profile as long as the following rules are followed: + + +The transfer shall occur on a Secure Transport Connection. + + +The two Application Entities involved in the transfer shall authenticate each other and shall confirm via the authentication that the other supports the Online Electronic Storage Secure Use Profile. + + +The receiving Application Entity shall reject the storage request and discard the received SOP Instance if the data integrity checks done after the transfer indicate that the SOP Instance was altered during transmission. + + +The transfer shall be confirmed using the push model of the Storage Commitment Service Class. Until it has completed this confirmation, the receiving Application Entity shall not forward the SOP Instance or Authorized Copies of the SOP instance to any other Application Entity. + + + Once confirmed that the receiving Application Entity has successfully committed the SOP Instance to storage, the sending Application Entity shall do one of the following to its local copy of the SOP Instance: + + +delete the SOP Instance, + + +change the SOP Instance Status to Not Specified (NS), + + +if the SOP Instance Status was Authorized Original (AO), change the SOP Instance Status to Authorized Copy (AC). + + + + + + + When communicating with an Application Entity that supports Online Electronic Storage an Application Entity that holds a SOP Instance whose SOP Instance Status is Authorized Original (AO) or Authorized Copy (AC) may send an Authorized Copy of the SOP Instance to another Application Entity as long as the following rules are followed: + + +The transfer shall occur on a Secure Transport Connection. + + +The two Application Entities involved in the transfer shall authenticate each other, and shall confirm via the authentication that the other supports the Online Electronic Storage Secure Use Profile. + + +The sending Application Entity shall set the SOP Instance Status to either Not Specified (NS) or Authorized Copy (AC) in the copy sent. The SOP Instance UID shall not change. + + +The receiving Application Entity shall reject the storage request and discard the copy if data integrity checks done after the transfer indicate that the SOP Instance was altered during transmission. + + + + + If communicating with a system that does not support the Online Electronic Storage Secure Use Profile, or if communication is not done over a Secure Transport Connection, then + + +A sending Application Entity that conforms to this Security Profile shall either set the SOP Instance Status to Not Specified (NS), or leave out the SOP Instance Status and associated parameters of any SOP Instances that the sending Application Entity sends out over the unsecured Transport Connection or to systems that do not support the Online Electronic Storage Secure Use Profile. + + +A receiving Application Entity that conforms to this Security Profile shall set the SOP Instance Status to Not Specified (NS) of any SOP Instance received over the unsecured Transport Connection or from systems that do not support the Online Electronic Storage Secure Use Profile. + + + + + The receiving Application Entity shall store SOP Instances in accordance with Level 2 as defined in the Storage Service Class (i.e., all Attributes, including Private Attributes), as required by the Storage Commitment Storage Service Class, and shall not coerce any Attribute other than SOP Instance Status, SOP Authorization Date and Time, Authorization Equipment Certification Number, and SOP Authorization Comment. + + + Other than changes to the SOP Instance Status, SOP Authorization Date and Time, Authorization Equipment Certification Number, and SOP Authorization Comment Attributes, as outlined above, or changes to group length Attributes to accommodate the aforementioned changes, the Application Entity shall not change any Attribute values. + + +
+
+
+ Basic Digital Signatures Secure Use Profile + + An implementation that validates and generates Digital Signatures may claim conformance to the Basic Digital Signatures Secure Use Profile. Any implementation that claims conformance to this Security Profile shall obey the following rules in handling Digital Signatures: + + +The implementation shall store any SOP Instances that it receives in such a way that it guards against any unauthorized tampering of the SOP Instance. + + +Wherever possible, the implementation shall validate the Digital Signatures within any SOP Instance that it receives. + + + If the implementation sends the SOP Instance to another Application Entity, it shall do the following: + + +remove any Digital Signatures that may have become invalid due to any allowed variations to the format of Attribute Values (e.g., trimming of padding, alternate representations of numbers), + + +generate one or more new Digital Signatures covering the Data Elements that the implementation was able to verify when the SOP Instance was received. + + + + +
+
+ Bit-preserving Digital Signatures Secure Use Profile + + An implementation that stores and forwards SOP Instances may claim conformance to the Bit-Preserving Digital Signatures Secure Use Profile. Any implementation that claims conformance to this Security Profile shall obey the following rules in handling Digital Signatures: + + +The implementation shall store any SOP Instances that it receives in such a way that when the SOP instance is forwarded to another Application Entity, the Value fields of all Attributes are bit-for-bit duplicates of the fields originally received. + + +The implementation shall not change the order of Items in a Sequence. + + + The implementation shall not remove or change any Data Element of any SOP Instance that it receives when sending that SOP Instance on to another Application Entity via DICOM. This includes any Digital Signatures received. + + Implementations may add new Data Elements that do not alter any existing Digital Signatures. + + + + The implementation shall utilize an explicit VR Transfer Syntax. + + Implementations that cannot use an explicit VR Transfer Syntax cannot conform to this Secure Use Profile, since it may not be able to verify Digital Signatures that are received with an implicit VR Transfer Syntax. + + + +The implementation shall not change the VR of any Data Element that it receives when it transmits that object to another Application Entity. + + +
+
+ Basic SR Digital Signatures Secure Use Profile + + Any implementation that claims conformance to this Security Profile shall obey the following rules when creating a Structured Report or Key Object Selection Document that includes Digital Signatures: + + +When the implementation signs a Structured Report or Key Object Selection Document SOP Instance the Digital Signatures shall be created in accordance with the Structured Report RSA Digital Signature Profile. + + +In every signed Structured Report or Key Object Selection Document SOP Instance created, all referenced SOP Instances listed in the Referenced SOP Sequence Items of the Current Requested Procedure Evidence Sequence (0040,A375) and Pertinent Other Evidence Sequence (0040,A385)shall include either a Referenced Digital Signature Sequence or a Referenced SOP Instance MAC Sequence. The references may include both. + + + The implementation claiming conformance shall outline in its conformance statement the conditions under which it will either sign or not sign a Structured Report or Key Object Selection Document. +
+
+ Audit Trail Message Format Profile + + To help assure healthcare privacy and security in automated systems, usage data need to be collected. These data will be reviewed by administrative staff to verify that healthcare data is being used in accordance with the healthcare provider's data security requirements and to establish accountability for data use. This data collection and review process is called security auditing and the data itself comprises the audit trail. Audit trails can be used for surveillance purposes to detect when interesting events might be happening that warrant further investigation. + This profile defines the format of the data to be collected and the minimum set of attributes to be captured by healthcare application systems for subsequent use by a review application. The data includes records of who accessed healthcare data, when, for what action, from where, and which patients' records were involved. No behavioral requirements are specified for when audit messages are generated, or for what action should be taken on their receipt. These are subject to local policy decisions and legal requirements. + Any implementation that claims conformance to this Security Profile shall: + + +format audit trail messages in accordance with the XML schema specified in in a fashion that allows those messages to be validated against that XML schema, following the general conventions specified in . + + +for the events described in this Profile comply with the restrictions specified by this Profile in , and describe in its conformance statement any extensions. + + An implementation may include implementation-specific extensions as long as the above conditions are met. + + + +describe in its conformance statement the events that it can detect and report, + + +describe in its conformance statement the processing it can perform upon receipt of a message + + +describe in its conformance statement how event reporting and processing can be configured + + + + Other profiles specify the transmission of audit messages. + +
+ DICOM Audit Message Schema + Implementations claiming conformance to this profile shall use the following XML schema to format audit trail messages. This schema is derived from the schema specified in RFC3881 IETF draft internet standard "Security Audit and Access Accountability XML Message Data Definitions for Healthcare Applications", according to W3C Recommendation "XML Schema Part 1: Structures," version 1.0, May 2001, and incorporates the DICOM extensions and restrictions outlined in . + This schema is provided in Relax NG Compact format. + + This schema can be converted into an equivalent XML schema or other electronic format. It includes some modifications to the RFC3881 schema that reflect field experience with audit message requirements. It extends the RFC3881 schema. + +
+ Audit Message Schema + The following is the content of the audit schema: + + +
+
+ Codes Used Within The Schema + The following value sets are defined in the audit schema above. These are not coded terminology. They are values whose meaning depends upon their use at the proper location within the message. +
+ Audit Source Type Code + The Audit Source Type Code values specify the type of source where an event originated. Codes from coded terminologies and implementation defined codes can also be used for the AuditSourceTypeCode. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Source Type Code Values
+ Value + + Meaning +
+ 1 + + End-user interface +
+ 2 + + Data acquisition device or instrument +
+ 3 + + Web server process tier in a multi-tier system +
+ 4 + + Application server process tier in a multi-tier system +
+ 5 + + Application server process tier in a multi-tier system +
+ 6 + + Security server, e.g., a domain controller +
+ 7 + + ISO level 1-3 network component +
+ 8 + + ISO level 4-6 operating software +
+ 9 + + External source, other or unknown type +
+
+
+ Participant Object Type Code Role + The Participant Object Type Code Role is an attribute of the ParticipantObjectIdentification, and is not extensible. This attribute may be omitted or one of the following values assigned. Coded terminologies are not supported. + Table + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Participant Object Type Code Roles
+ Value + + Meaning + + Likely associated Participant Object Type Code +
+ 1 + + Patient + + 1 - Person +
+ 2 + + Location + + 3 - Organization +
+ 3 + + Report + + 2 - System Object +
+ 4 + + Resource + + 1 - Person, or + 3 - Organization +
+ 5 + + Master File + + 2 - System Object +
+ 6 + + User + + 1 - Person, or + 2 - System Object +
+ 7 + + List + + 2 - System Object +
+ 8 + + Doctor + + 1 - Person +
+ 9 + + Subscriber + + 3 - Organization +
+ 10 + + Guarantor + + 1 - Person, or + 3 - Organization +
+ 11 + + Security User Entity + + 1 - Person, or + 2 - System Object +
+ 12 + + Security User Group + + 2 - System Object +
+ 13 + + Security Resource + + 2 - System Object +
+ 14 + + Security Granularity Definition + + 2 - System Object +
+ 15 + + Provider + + 1 - Person, or + 3 - Organization +
+ 16 + + Data Destination + + 2 - System Object +
+ 17 + + Data Repository + + 2 - System Object +
+ 18 + + Schedule + + 2 - System Object +
+ 19 + + Customer + + 3 - Organization +
+ 20 + + Job + + 2 - System Object +
+ 21 + + Job Stream + + 2 - System Object +
+ 22 + + Table + + 2 - System Object +
+ 23 + + Routing Criteria + + 2 - System Object +
+ 24 + + Query + + 2 - System Object +
+
+
+ Participant Object Data Life Cycle + The Participant Object Data Life Cycle is an attribute of the ParticipantObjectIdentification, and is not extensible. This attribute may be omitted or one of the following values assigned. Coded terminologies are not supported. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Participant Object Data Life Cycle Values
+ Value + + Meaning +
+ 1 + + Origination or Creation +
+ 2 + + Import or Copy from original +
+ 3 + + Amendment +
+ 4 + + Verification +
+ 5 + + Translation +
+ 6 + + Access or Use +
+ 7 + + De-identification +
+ 8 + + Aggregation, summarization, derivation +
+ 9 + + Report +
+ 10 + + Export or Copy to target +
+ 11 + + Disclosure +
+ 12 + + Receipt of Disclosure +
+ 13 + + Archiving +
+ 14 + + Logical Deletion +
+ 15 + + Permanent erasure or physical destruction +
+
+
+ Participant Object ID Type Code + The Participant Object ID Type Code describes the identifier that is contained in Participant Object ID. Codes from coded terminologies and implementation defined codes can also be used for the ParticipantObjectTypeCodeRole. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Participant Object ID Type Code Values
+ Value + + Meaning + + Likely associated Participant Object Type Code +
+ 1 + + Medical Record Number + + 1 - Person +
+ 2 + + Patient Number + + 1 - Person +
+ 3 + + Encounter Number + + 1 - Person +
+ 4 + + Enrollee Number + + 1 - Person +
+ 5 + + Social Security Number + + 1 - Person +
+ 6 + + Account Number + + 1 - Person, or + 3 - Organization +
+ 7 + + Guarantor Number + + 1 - Person, or + 3 - Organization +
+ 8 + + Report Name + + 2 - System Object +
+ 9 + + Report Number + + 2 - System Object +
+ 10 + + Search Criteria + + 2 - System Object +
+ 11 + + User Identifier + + 1 - Person, or + 2 - System Object +
+ 12 + + URI + + 2 - System Object +
+
+
+ +
+
+ General Message Format Conventions + + The following table lists the primary fields from the message schema specified in A.5.1, with additional instructions, conventions, and restrictions on how DICOM applications shall fill in the field values. + The field names are leaf elements and attributes that are in the DICOM Audit Message Schema (see ). + Note that these fields may be enclosed in other XML elements, as specified by the schema. + + This schema, codes, and content were originally derived from RFC3881. + RFC3881 is not being maintained or updated by the IETF, and has gradually diverged from the DICOM schema and codes. + Other documents exist that refer to RFC3881 as the underlying standard. + RFC3881 does not include corrections and additions to the audit schema made in DICOM since 2004. + + In subsequent tables the following notation Is used for optionality: + + +M + +This element or attribute is mandatory + + + +U + +This element or attribute is user optional. The creator may include it or omit it. + + + +MC + +This element or attribute is mandatory if a specified condition is true. + + + +UC + +This element or attribute may be present only if a specified condition is true, if the user chooses to include it. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
General Message Format
+ + Field Name + + Opt. + + Description + + Additional Conditions on Field Format/Value +
+ Event + + EventID + + M + + Identifier for a specific audited event. + + The identifier for the family of event. E.g., "User Authentication". + D + +
+ EventActionCode + + U + + Indicator for type of action performed during the event that generated the audit. + + + +C + +Create a new database object, such as Placing an Order + + + +R + +Read/View/Print/Query Display or print data, such as a Doctor Census + + + +U + +Update data, such as Revise Patient Information + + + +D + +Delete items, such as a master file record + + + +E + +Perform a system or application function such as log-on, program execution, or use of an object's method + + + +
+ EventDateTime + + M + + Universal coordinated time (UTC), i.e., a date/time specification that is unambiguous as to local time zones. + + The time at which the audited event occurred.See + +
+ EventOutcomeIndicator + + M + + Indicates whether the event succeeded or failed. + + + +0 + +Success + + + +4 + +Minor failure; action restarted, e.g., invalid password with first retry + + + +8 + +Serious failure; action terminated, e.g., invalid password with excess retries + + + +12 + +Major failure; action made unavailable, e.g., user account disabled due to excessive invalid log-on attempts + + + + When a particular event has some aspects that succeeded and some that failed, then one message shall be generated for successful actions and one message for the failed actions (i.e., not a single message with mixed results). +
+ EventTypeCode + + U + + Identifier for the category of event. + + The specific type(s) within the family applicable to the event, e.g., "User Login". + D + +
+ Active Participant (multi-valued) + + UserID + + M + + Unique identifier for the user actively participating in the event. + + See . +
+ AlternativeUserID + + U + + Alternative unique identifier for the user. + + See . +
+ UserName + + U + + The human-meaningful name for the user. + + See . +
+ UserIsRequestor + + M + + Indicator that the user is or is not the requestor, or initiator, for the event being audited. + + Used to identify which of the participants initiated the transaction being audited. If the audit source cannot determine which of the participants is the requestor, then the field shall be present with the value FALSE in all participants. + The system shall not identify multiple participants as UserIsRequestor. If there are several known requestors, the reporting system shall pick only one as UserIsRequestor. +
+ RoleIDCode + + U + + Specification of the role(s) the user plays when performing the event, as assigned in role-based access control security. + + D + + + Usage of this field is refined in the individual message descriptions below. Other additional roles may also be present, since this is a multi-valued field. + +
+ NetworkAccessPointTypeCode + + U + + An identifier for the type of network access point. + + See . +
+ NetworkAccessPointID + + U + + An identifier for the network access point of the user device This could be a device id, IP address, or some other identifier associated with a device. +
+ Audit Source + + AuditEnterpriseSiteID + + U + + Logical source location within the healthcare enterprise network, e.g., a hospital or other provider location within a multi-entity provider group. + + Serves to further qualify the Audit Source ID, since Audit Source ID is not required to be globally unique. +
+ AuditSourceID + + M + + Identifier of the source. + + The identification of the system that detected the auditable event and created this audit message. Although often the audit source is one of the participants, it could also be an external system that is monitoring the activities of the participants (e.g., an add-on audit-generating device). +
+ AuditSourceTypeCode + + U + + Code specifying the type of source. + + See . + E.g., an acquisition device might use "2" (data acquisition device), a PACS/RIS system might use "4 "(application server process). +
+ Participant Object (multi-valued) + + ParticipantObjectTypeCode + + U + + Code for the participant object type being audited. This value is distinct from the user's role or any user relationship to the participant object. + + + +1 + +Person + + + +2 + +System Object + + + +3 + +Organization + + + +4 + +Other + + + +
+ ParticipantObjectTypeCodeRole + + U + + Code representing the functional application role of Participant Object being audited. + + See . +
+ ParticipantObjectDataLifeCycle + + U + + Identifier for the data life-cycle stage for the participant object. This can be used to provide an audit trail for data, over time, as it passes through the system. + + See . +
+ ParticipantObjectIDTypeCode + + M + + Describes the identifier that is contained in Participant Object ID. + + See and + + + Usage of this field is refined in the individual message descriptions below. Multiple roles may also be present, since this is a multi-valued field. + +
+ ParticipantObjectSensitivity + + U + + Denotes policy-defined sensitivity for the Participant Object ID such as VIP, HIV status, mental health status, or similar topics. + + Locally defined terms. +
+ ParticipantObjectID + + M + + Identifies a specific instance of the participant object. + + Usage refined by individual message descriptions +
+ ParticipantObjectName + + U + + An instance-specific descriptor of the Participant Object ID audited, such as a person's name. + + Usage refined by individual message descriptions +
+ ParticipantObjectQuery + + U + + The actual query for a query-type participant object. + + Usage refined by individual message descriptions +
+ ParticipantObjectDetail + + U + + Implementation-defined data about specific details of the object accessed or used. + + + This element is a Type-value pair. The "type" attribute is an implementation-defined text string. The "value" attribute is base 64 encoded data. The value is suitable for conveying binary data. +
+ SOPClass + + MC + + + + The UIDs of SOP classes referred to in this participant object. + Required if ParticipantObjectIDTypeCode is (110180, DCM, "Study Instance UID") and any of the optional fields (AccessionNumber, ContainsMPPS, NumberOfInstances, ContainsSOPInstances,Encrypted,Anonymized) are present in this Participant Object. May be present if ParticipantObjectIDTypeCode is (110180, DCM, "Study Instance UID") even though none of the optional fields are present. +
+ Accession + + U + + + + An Accession Number(s) associated with this participant object. +
+ MPPS + + U + + + + An MPPS Instance UID(s) associated with this participant object. +
+ NumberOfInstances + + U + + + + The number of SOP Instances referred to by this participant object. +
+ Instance + + U + + + + SOP Instance UID value(s) + +Including the list of SOP Instances can create a fairly large audit message. Under most circumstances, the list of SOP Instance UIDs is not needed for audit purposes. + +
+ Encrypted + + U + + + + A single value of True or False indicating whether or not the data was encrypted. + + If there was a mix of encrypted and non-encrypted data, then create two event reports. + +
+ Anonymized + + U + + + + A single value of True or False indicating whether or not all patient identifying information was removed from the data +
+ ParticipantObjectContainsStudy + + U + + + + A Study Instance UID, which may be used when the ParticipantObjectIDTypeCode is not (110180, DCM, "Study Instance UID"). +
+
+ UserID + + If the participant is a person, then the User ID shall be the identifier used for that person on this particular system, in the form of loginName@domain-name. + If the participant is an identifiable process, the UserID selected shall be one of the identifiers used in the internal system logs. For example, the User ID may be the process ID as used within the local operating system in the local system logs. If the participant is a node, then User ID may be the node name assigned by the system administrator. Other participants such as threads, relocatable processes, web service end-points, web server dispatchable threads, etc. will have an appropriate identifier. The implementation shall document in the conformance statement the identifiers used, see . The purpose of this requirement is to allow matching of the audit log identifiers with internal system logs on the reporting systems. . + When importing or exporting data, e.g., by means of media, the UserID field is used both to identify people and to identify the media itself. When the Role ID Code is EV(110154, DCM, "Destination Media") or EV(110155, DCM, "Source Media"), the UserID may be: + + +a URI (the preferred form) identifying the source or destination, + + +an email address of the form "mailto:user@address" + + +a description of the media type (e.g., DVD) together with a description of its identifying label, as a free text field, + + +a description of the media type (e.g., paper, film) together with a description of the location of the media creator (i.e., the printer). + + + The UserID field for Media needs to be highly flexible given the large variety of media and transports that might be used. +
+
+ AlternativeUserID + + If the participant is a person, then Alternative User ID shall be the identifier used for that person within an enterprise for authentication purposes, for example, a Kerberos Username (user@realm). If the participant is a DICOM application, then Alternative User ID shall be one or more of the AE Titles that participated in the event. Multiple AE titles shall be encoded as: + AETITLES= + aetitle1;aetitle2;… + + When importing or exporting data, e.g., by means of media, the Alternative UserID field is used either to identify people or to identify the media itself. When the Role ID Code is (110154, DCM, "Destination Media") or (110155, DCM, "Source Media"), the Alternative UserID may be any machine readable identifications on the media, such as media serial number, volume label, or DICOMDIR SOP Instance UID. +
+
+ Username + + A human readable identification of the participant. If the participant is a person, the person's name shall be used. If the participant is a process, then the process name shall be used. +
+
+ Multi-homed Nodes + + The NetworkAccessPointTypeCode and NetworkAccessPointID can be ambiguous for systems that have multiple physical network connections. For these multi-homed nodes a single DNS name or IP address shall be selected and used when reporting audit events. DICOM does not require the use of a specific method for selecting the network connection to be used for identification, but it must be the same for all of the audit messages generated for events on that node. +
+
+ EventDateTime + The EventDateTime is the date and time that the event being reported took place. Some events have a significant duration. In these cases, a date and time shall be chosen by a method that is consistent and appropriate for the event being reported. + The EventDateTime shall include the time zone information. + Creators of audit messages may support leap-seconds, but are not required to. Recipients of audit messages shall be able to process messages with leap-second information. +
+
+ ParticipantObjectTypeCodeRole + The ParticipantObjectTypeCodeRole identifies the role that the object played in the event that is being reported. Most events involve multiple participating objects. ParticipantObjectTypeCodeRole identifies which object took which role in the event. It also covers agents, multi-purpose entities, and multi-role entities. For the purpose of the event one primary role is chosen. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParticipantObjectTypeCodeRole
+ Code + + Short Description + + Description +
+ 1 + + Patient + + This object is the patient that is the subject of care related to this event. It is identifiable by patient ID or equivalent. The patient may be either human or animal. +
+ 2 + + Location + + This is a location identified as related to the event. This is usually the location where the event took place. Note that for shipping, the usual events are arrival at a location or departure from a location. +
+ 3 + + Report + + This object is any kind of persistent document created as a result of the event. This could be a paper report, film, electronic report, DICOM Study, etc. Issues related to medical records life cycle management are conveyed elsewhere. +
+ 4 + + Resource + + (deprecated) +
+ 5 + + Master File + + This is any configurable file used to control creation of documents or behavior. Examples include the objects maintained by the HL7 Master File transactions, Value Sets, etc. +
+ 6 + + User + + A human participant not otherwise identified by some other category +
+ 7 + + List + + (deprecated) +
+ 8 + + Doctor + + A person who is providing or performing care related to the event, generally a physician. The key distinction between doctor and provider is the nature of their participation. The doctor is the human who actually performed the work. The provider is the human or organization that is responsible for the work. +
+ 9 + + Subscriber + + A person or system that is being notified as part of the event. This is relevant in situations where automated systems provide notifications to other parties when an event took place. +
+ 10 + + Guarantor + + Insurance company, or any other organization who accepts responsibility for paying for the healthcare event. +
+ 11 + + Security User Entity + + A person or active system object involved in the event with a security role. +
+ 12 + + Security User Group + + (deprecated) +
+ 13 + + Security Resource + + A passive object, such as a role table, that is relevant to the event. +
+ 14 + + Security Granularity Definition + + (deprecated) Relevant to certain RBAC security methodologies. +
+ 15 + + Provider + + A person or organization responsible for providing care. This encompasses all forms of care, licensed or otherwise, and all sorts of teams and care groups. Note, the distinction between providers and the doctor that actually provided the care to the patient. +
+ 16 + + Data Destination + + The destination for data transfer, when some other role is not appropriate. +
+ 17 + + Data Archive + + A source or destination for data transfer that acts as an archive, database, or similar role. +
+ 18 + + Schedule + + An object that holds schedule information. This could be an appointment book, availability information, etc. +
+ 19 + + Customer + + An organization or person that is the recipient of services. This could be an organization that is getting services for a patient, or a person that is getting services for an animal. +
+ 20 + + Job + + An order, task, work item, procedure step, or other description of work to be performed. E.g., a particular instance of an MPPS. +
+ 21 + + Job Stream + + A list of jobs or a system that provides lists of jobs. E.g., an MWL SCP. +
+ 22 + + Table + + (Deprecated) +
+ 23 + + Routing Criteria + + An object that specifies or controls the routing or delivery of items. For example, a distribution list is the routing criteria for mail. The items delivered may be documents, jobs, or other objects. +
+ 24 + + Query + + The contents of a query. This is used to capture the contents of any kind of query. For security surveillance purposes knowing the queries being made is very important. +
+ 25 + + Data Source + + The source or origin of data, when there is no other matching role available. +
+ 26 + + Processing Element + + A data processing element that creates, analyzes, modifies, or manipulates data as part of this event. +
+
+
+
+ DICOM Specific Audit Messages + + The following subsections define message specializations for use by implementations that claim conformance to the DICOM Audit Trail Profile. Any field (i.e., XML element and associated attributes) not specifically mentioned in the following tables shall follow the conventions specified in A.5.1 and A.5.2. + An implementation claiming conformance to this Profile that reports an activity covered by one of the audit messages defined by this Profile shall use the message format defined in this Profile. However, a system claiming conformance to this Profile is not required to send a message each time the activity reported by that audit message occurs. It is expected that the triggering of audit messages would be configurable on an individual basis, to be able to balance network load versus the severity of threats, in accordance with local security policies. + + + + It is a system design issue outside the scope of DICOM as to what entity actually sends an audit event and when. For example, a Query message could be generated by the entity where the query originated, by the entity that eventually would respond to the query, or by a monitoring entity not directly involved with the query, but that generates audit messages based on monitored network traffic. + + + To report events that are similar to the events described here, these definitions can be used as the basis for extending the schema. + + + + In the subsequent tables, the information entity column indicates the relationship between real world entities and the information elements encoded into the message. +
+ Application Activity + + This audit message describes the event of an Application Entity starting or stopping. This is closely related to the more general case of any kind of application startup or shutdown, and may be suitable for those purposes also. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Application Activity Message
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110100, DCM, "Application Activity") + +
+ EventActionCode + + M + + Enumerated Value + E = Execute +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + M + + DT (110120, DCM, "Application Start") + + DT (110121, DCM, "Application Stop") + +
+ Active Participant: + Application started (1) + + UserID + + M + + The identity of the process started or stopped formatted as specified in A.5.2.1. +
+ AlternativeUserID + + MC + + If the process supports DICOM, then the AE Titles as specified in A.5.2.2. +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110150, DCM, "Application") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Persons and or processes that started the Application (0..N) + + UserID + + M + + The person or process starting or stopping the Application +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110151, DCM, "Application Launcher") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ No Participant Objects are needed for this message. +
+
+ Audit Log Used + + This message describes the event of a person or process reading a log of audit trail information. + + For example, an implementation that maintains a local cache of audit information that has not been transferred to a central collection point might generate this message if its local cache were accessed by a user. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Log Used Message
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110101, DCM, "Audit Log Used") + +
+ EventActionCode + + M + + Shall be enumerated value: + R = read +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + Persons and or processes that started the Application (1..2) + + UserID + + M + + The person or process accessing the audit trail. If both are known, then two active participants shall be included (both the person and the process). +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + Identity of the audit log (1) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 13 = security resource +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 12 = URI +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The URI of the audit log +
+ ParticipantObjectName + + U + + Shall be: "Security Audit Log" +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+ SOPClass + + U + + See + +
+ Accession + + U + + See + +
+ NumberOfInstances + + U + + See + +
+ Instances + + U + + See + +
+ Encrypted + + U + + See + +
+ Anonymized + + U + + See + +
+ ParticipantObjectContainsStudy + + U + + See + +
+
+
+ Begin Transferring DICOM Instances + + This message describes the event of a system beginning to transfer a set of DICOM instances from one node to another node within control of the system's security domain. This message may only include information about a single patient. + + A separate Instances Transferred message is defined for transfer completion, allowing comparison of what was intended to be sent and what was actually sent. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Begin Transferring DICOM Instances
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110102, DCM, "Begin Transferring DICOM Instances") + +
+ EventActionCode + + M + + Shall be: E = Execute +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + Process Sending the Data (1) + + UserID + + M + + The identity of the process sending the data. +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110153, DCM, "Source Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Process receiving the data (1) + + UserID + + M + + The identity of the process receiving the data. +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110152, DCM, "Destination Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Other Participants (0..N) + + UserID + + M + + The identity of any other participants that might be involved and known, especially third parties that are the requestor +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + Studies being transferred (1..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + Element "ContainsSOPClass" with one or more SOP Class UID values +
+ ParticipantObjectDescription + + U + + not specialized +
+ SOPClass + + MC + + not specialized +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+ Participating Object: + Patient (1) + + ParticipantObjectTypeCode + + M + + Shall be: 1 = person +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 1 = patient +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 2 = patient ID +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+
+
+ Data Export + + This message describes the event of exporting data from a system, meaning that the data is leaving control of the system's security domain. Examples of exporting include printing to paper, recording on film, conversion to another format for storage in an EHR, writing to removable media, or sending via e-mail. Multiple patients may be described in one event message. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Data Export
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110106, DCM, "Export") + +
+ EventActionCode + + M + + Shall be: R = Read +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + Remote Users and Processes (0..n) + + UserID + + M + + The identity of the remote user or process receiving the data +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + See + +
+ RoleIDCode + + M + + EV (110152, DCM, "Destination Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + User or Process Exporting the data(1..2) + + UserID + + M + + The identity of the local user or process exporting the data. If both are known, then two active participants shall be included (both the person and the process). +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + See + +
+ RoleIDCode + + M + + EV (110153, DCM, "Source Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Media (1) + + UserID + + M + + See + +
+ AlternativeUserID + + U + + See + +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + Shall be FALSE +
+ RoleIDCode + + M + + EV (110154, DCM, "Destination Media") + +
+ NetworkAccessPointTypeCode + + MC + + Required if being exported to other than physical media, e.g., to a network destination rather than to film, paper or CD. May be present otherwise. +
+ NetworkAccessPointID + + MC + + Required if Net Access Point Type Code is present. May be present otherwise. +
+ MediaIdentifier + + MC + + Volume ID, URI, or other identifier for media. + Required if digital media. May be present otherwise. +
+ MediaType + + M + + Values selected from D + +
+ Participating Object: + Studies (0..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+ SOPClass + + MC + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+ Participating Object: + Patients (1..N) + + ParticipantObjectTypeCode + + M + + Shall be: 1 = person +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 1 = patient +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 2 = patient ID +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+
+ UserIsRequestor + A single user (either local or remote) shall be identified as the requestor, i.e., UserIsRequestor with a value of TRUE. This accommodates both push and pull transfer models for media. +
+
+
+ Data Import + + This message describes the event of importing data into an organization, implying that the data now entering the system was not under the control of the security domain of this organization. Transfer by media within an organization is often considered a data transfer rather than a data import event. An example of importing is creating new local instances from data on removable media. Multiple patients may be described in one event message. + A single user (either local or remote) shall be identified as the requestor, i.e., UserIsRequestor with a value of TRUE. This accommodates both push and pull transfer models for media. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Data Import
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110107, DCM, "Import") + +
+ EventActionCode + + M + + Shall be: C = Create +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + User or Process Importing the data (1..n) + + UserID + + M + + The identity of the local user or process importing the data. +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + See + +
+ RoleIDCode + + M + + EV (110152, DCM, "Destination Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Source Media (1) + + UserID + + M + + See + +
+ AlternativeUserID + + U + + See + +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + Shall be FALSE +
+ RoleIDCode + + M + + EV (110155, DCM, "Source Media") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + MC + + Shall be present if Net Access Point Type Code is present. +
+ MediaIdentifier + + M + + Volume ID, URI, or other identifier for media +
+ MediaType + + M + + Values selected from D + +
+ Active Participant: + Source (0..n) + + UserID + + M + + See + +
+ AlternativeUserID + + U + + See + +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + See + +
+ RoleIDCode + + M + + EV (110153, DCM, "Source Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + MC + + Shall be present if Net Access Point Type Code is present. +
+ Participating Object: + Studies (0..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + Not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+ SOPClass + + MC + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+ Participating Object: + Patients (1..N) + + ParticipantObjectTypeCode + + M + + Shall be: 1 = person +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 1 = patient +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 2 = patient ID +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+
+
+ DICOM Instances Accessed + + This message describes the event of DICOM SOP Instances being viewed, utilized, updated, or deleted. This message shall only include information about a single patient and can be used to summarize all activity for several studies for that patient. This message records the studies to which the instances belong, not the individual instances. + If all instances within a study are deleted, then the EV(110105, DCM, "DICOM Study Deleted") event shall be used, see . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for DICOM Instances Accessed
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110103, DCM, "DICOM Instances Accessed") + +
+ EventActionCode + + M + + Enumerated value: + C = create + R = read + U = update + D = delete +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + Person and or Process manipulating the data + (1..2) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + Studies (1..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + Not specialized +
+ ParticipantObjectDescription + + U + + Not specialized +
+ SOPClass + + MC + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+ Participating Object: + Patient (1) + + ParticipantObjectTypeCode + + M + + Shall be: 1 = person +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 1 = patient +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 2 = patient ID +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+
+
+ DICOM Instances Transferred + + This message describes the event of the completion of transferring DICOM SOP Instances between two Application Entities. This message may only include information about a single patient. + + This message may have been preceded by a Begin Transferring Instances message. The Begin Transferring Instances message conveys the intent to store SOP Instances, while the Instances Transferred message records the completion of the transfer. Any disagreement between the two messages might indicate a potential security breach. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for DICOM Instances Transferred
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110104, DCM, "DICOM Instances Transferred") + +
+ EventActionCode + + M + + Enumerated Value: + C = (create) if the receiver did not hold copies of the instances transferred + R = (read) if the receiver already holds copies of the SOP Instances transferred, and has determined that no changes are needed to the copies held. + U = (update) if the receiver is altering its held copies to reconcile differences between the held copies and the received copies. + If the Audit Source is either not the receiver, or otherwise does not know whether or not the instances previously were held by the receiving node, then use "R" = (Read). +
+ EventDateTime + + M + + Shall be the time when the transfer has completed +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + Process that sent the data (1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110153, DCM, "Source Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + The process that received the data. (1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110152, DCM, "Destination Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Other participants that are known, especially third parties that are the requestor (0..N) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + Studies being transferred (1..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + Not specialized +
+ ParticipantObjectDescription + + U + + Not specialized +
+ SOPClass + + MC + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+ Participating Object: + Patient (1) + + ParticipantObjectTypeCode + + M + + Shall be: 1 = person +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 1 = patient +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 2 = patient ID +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+
+
+ DICOM Study Deleted + + This message describes the event of deletion of one or more studies and all associated SOP Instances in a single action. This message shall only include information about a single patient. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for DICOM Study Deleted
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110105, DCM, "DICOM Study Deleted") + +
+ EventActionCode + + M + + Shall be: D = delete +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + the person or process deleting the study (1..2) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + Studies being transferred (1..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + Not specialized +
+ ParticipantObjectDescription + + U + + Not specialized +
+ SOPClass + + MC + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+ Participating Object: + Patient (1) + + ParticipantObjectTypeCode + + M + + Shall be: 1 = person +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 1 = patient +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Shall be: 2 = patient ID +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not specialized +
+
+
+ Network Entry + + This message describes the event of a system, such as a mobile device, intentionally entering or leaving the network. + + The machine should attempt to send this message prior to detaching. If this is not possible, it should retain the message in a local buffer so that it can be sent later. The mobile machine can then capture audit messages in a local buffer while it is outside the secure domain. When it is reconnected to the secure domain, it can send the detach message (if buffered), followed by the buffered messages, followed by a mobile machine message for rejoining the secure domain. The timestamps on these messages is the time that the event was noticed to have occurred, not the time that the message is sent. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Network Entry
+ Real World Entities + + Field Name + + Opt. + + Value +
+ Event + + EventID + + M + + EV (110108, DCM, "Network Entry") + +
+ EventActionCode + + M + + Shall be: E = Execute +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + M + + EV (110124, DCM, "Attach")EV (110125, DCM, "Detach") + +
+ Active Participant: + Node or System entering or leaving the network (1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + Shall be FALSE +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ No Participant Objects are needed for this message. +
+
+ Query + + This message describes the event of a Query being issued or received. The message does not record the response to the query, but merely records the fact that a query was issued. For example, this would report queries using the DICOM SOP Classes: + + +Modality Worklist + + +UPS Pull + + +UPS Watch + + +Composite Instance Query + + + + + +The response to a query may result in one or more Instances Transferred or Instances Accessed messages, depending on what events transpire after the query. If there were security-related failures, such as access violations, when processing a query, those failures should show up in other audit messages, such as a Security Alert message. + + +Non-DICOM queries may also be captured by this message. The Participant Object ID Type Code, the Participant Object ID, and the Query fields may have values related to such non-DICOM queries. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Query
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110112, DCM, "Query") + +
+ EventActionCode + + M + + Shall be: E = Execute +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ Active Participant: + Process Issuing the Query (1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110153, DCM, "Source Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + The process that will respond to the query (1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + M + + EV (110152, DCM, "Destination Role ID") + +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Other Participants that are known, especially third parties that requested the query (0..N) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + SOP Queried and the Query (1) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + M + + Shall be: 3 = report +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + DT (110181, DCM, "SOP Class UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + If the ParticipantObjectIDTypeCode is (110181, DCM, "SOP Class UID"), then this field shall hold the UID of the SOP Class being queried +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + M + + If the ParticipantObjectIDTypeCode is (110181, DCM, "SOP Class UID"), then this field shall hold the Dataset of the DICOM query, xs:base64Binary encoded. Otherwise, it shall be the query in the format of the protocol used. +
+ ParticipantObjectDetail + + MC + + Required if the ParticipantObjectIDTypeCode is (110181, DCM, "SOP Class UID") + + A ParticipantObjectDetail element with the XML attribute "TransferSyntax" shall be present. The value of the Transfer Syntax attribute shall be the UID of the transfer syntax of the query. The element contents shall be xs:base64Binary encoding. The Transfer Syntax shall be a DICOM Transfer Syntax. +
+ ParticipantObjectDescription + + U + + not specialized +
+ SOPClass + + U + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+
+
+ Security Alert + + This message describes any event for which a node needs to report a security alert, e.g., a node authentication failure when establishing a secure communications channel. + + The Node Authentication event can be used to report both successes and failures. If reporting of success is done, this could generate a very large number of audit messages, since every authenticated DICOM association, HL7 transaction, and HTML connection should result in a successful node authentication. It is expected that in most situations only the failures will be reported. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Security Alert
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110113, DCM, "Security Alert") + +
+ EventActionCode + + M + + Shall be: E = Execute +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + Success implies an informative alert. The other failure values imply warning codes that indicate the severity of the alert. A Minor or Serious failure indicates that mitigation efforts were effective in maintaining system security. A Major failure indicates that mitigation efforts may not have been effective, and that the security system may have been compromised. +
+ EventTypeCode + + M + + Values selected from D. +
+ Active Participant: + Reporting Person and/or Process (1..2) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Active Participant: + Performing Persons or Processes (0..N) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + Shall be FALSE +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Participating Object: + Alert Subject (0..N) + + ParticipantObjectTypeCode + + M + + Shall be: 2 = system +
+ ParticipantObjectTypeCodeRole + + U + + Defined Terms: + 5 = master file + 13 = security resource +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + Defined Terms: + 12 = URI(110182, DCM, "Node ID") = Node Identifier +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + For a ParticipantObjectIDTypeCode of 12 (URI), then this value shall be the URI of the file or other resource that is the subject of the alert. + For a ParticipantObjectIDTypeCode of (110182, DCM, "Node ID") then the value shall include the identity of the node that is the subject of the alert either in the form ofnode_name@domain_nameor as an IP address. + Otherwise, the value shall be an identifier of the type specified by ParticipantObjectIDTypeCode of the subject of the alert. +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + M + + An element with the Attribute "type" equal to "Alert Description" shall be present with a free text description of the nature of the alert as the value +
+ ParticipantObjectDescription + + U + + not specialized +
+ SOPClass + + U + + See + +
+ Accession + + U + + not specialized +
+ NumberOfInstances + + U + + not specialized +
+ Instances + + U + + not specialized +
+ Encrypted + + U + + not specialized +
+ Anonymized + + U + + not specialized +
+
+
+ User Authentication + This message describes the event that a user has attempted to log on or log off. This report can be made regardless of whether the attempt was successful or not. No Participant Objects are needed for this message. + + The user usually has UserIsRequestor TRUE, but in the case of a logout timer, the Node might be the UserIsRequestor. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for User Authentication
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110114, DCM, "User Authentication") + +
+ EventActionCode + + M + + Shall be: E = Execute +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + M + + Defined Terms: + EV (110122, DCM, "Login") + + EV (110123, DCM, "Logout") + +
+ Active Participant: + Person Authenticated or claimed + (1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + M + + not specialized +
+ NetworkAccessPointID + + M + + not specialized +
+ Active Participant: + Node or System performing authentication (0..1) + + UserID + + M + + not specialized +
+ AlternativeUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + M + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+
+
+ Order Record + + This message describes the event of an order being created, modified, accessed, or deleted. This message may only include information about a single patient. + + An order record typically is managed by a non-DICOM system. However, DICOM applications often manipulate order records, and thus may be obligated by site security policies to record such events in the audit logs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Order Record
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110109, DCM, "Order Record") + +
+ EventActionCode + + M + + Enumerated value: + C = create + R = read + U = update + D = delete +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ User (1..2) + + UserID + + M + + The identity of the person or process manipulating the data. If both the person and the process are known, both shall be included. +
+ AlternateUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + U + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Patient (1) + + ParticipantObjectTypeCode + + M + + EV 1 (person) +
+ ParticipantObjectTypeCodeRole + + M + + EV 1 (patient) +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV 2 (patient ID) +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not further specialized +
+
+
+ Patient Record + + This message describes the event of a patient record being created, modified, accessed, or deleted. + + There are several types of patient records managed by both DICOM and non-DICOM system. DICOM applications often manipulate patient records managed by a variety of systems, and thus may be obligated by site security policies to record such events in the audit logs. This audit event can be used to record the access or manipulation of patient records where specific DICOM SOP Instances are not involved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Patient Record
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110110, DCM, "Patient Record") + +
+ EventActionCode + + M + + Enumerated value: + C = create + R = read + U = update + D = delete +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ User (1..2) + + UserID + + M + + The identity of the person or process manipulating the data. If both are known, then two active participants shall be included (both the person and the process). +
+ AlternateUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + U + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Patient (1) + + ParticipantObjectTypeCode + + M + + EV 1 (person) +
+ ParticipantObjectTypeCodeRole + + M + + EV 1 (patient) +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV 2 (patient ID) +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not further specialized +
+
+
+ Procedure Record + This message describes the event of a procedure record being created, accessed, modified, accessed, or deleted. This message may only include information about a single patient. + + + + DICOM applications often manipulate procedure records, e.g. with MPPS update. Modality Worklist query events are described by the Query event message. + + + The same accession number may appear with several order numbers. The Study participant fields or the entire message may be repeated to capture such many to many relationships. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Audit Message for Procedure Record
+ Real World Entities + + Field Name + + Opt. + + Value Constraints +
+ Event + + EventID + + M + + EV (110111, DCM, "Procedure Record") + +
+ EventActionCode + + C + + Enumerated value: + C = create + R = read + U = update + D = delete +
+ EventDateTime + + M + + not specialized +
+ EventOutcomeIndicator + + M + + not specialized +
+ EventTypeCode + + U + + not specialized +
+ User (1..2) + + UserID + + M + + The identity of the person or process manipulating the data. If both are known, then two active participants shall be included (both the person and the process). +
+ AlternateUserID + + U + + not specialized +
+ UserName + + U + + not specialized +
+ UserIsRequestor + + U + + not specialized +
+ RoleIDCode + + U + + not specialized +
+ NetworkAccessPointTypeCode + + U + + not specialized +
+ NetworkAccessPointID + + U + + not specialized +
+ Study (0..N) + + ParticipantObjectTypeCode + + M + + EV 2 (system) +
+ ParticipantObjectTypeCodeRole + + M + + EV 3 (report) +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV (110180, DCM, "Study Instance UID") + +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The Study Instance UID +
+ ParticipantObjectName + + U + + not specialized +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + Not further specialized +
+ ParticipantObjectDescription + + U + + Not further specialized +
+ SOPClass + + MC + + not further specialized +
+ Accession + + U + + not further specialized +
+ NumberOfInstances + + U + + not further specialized +
+ Instances + + U + + not further specialized +
+ Encrypted + + U + + not further specialized +
+ Anonymized + + U + + not further specialized +
+ Patient (1) + + ParticipantObjectTypeCode + + M + + EV 1 (person) +
+ ParticipantObjectTypeCodeRole + + M + + EV 1 (patient) +
+ ParticipantObjectDataLifeCycle + + U + + not specialized +
+ ParticipantObjectIDTypeCode + + M + + EV 2 (patient ID) +
+ ParticipantObjectSensitivity + + U + + not specialized +
+ ParticipantObjectID + + M + + The patient ID +
+ ParticipantObjectName + + U + + The patient name +
+ ParticipantObjectQuery + + U + + not specialized +
+ ParticipantObjectDetail + + U + + not specialized +
+ ParticipantObjectDescription + + U + + not further specialized +
+
+ +
+
+
+ Audit Trail Message Transmission Profile - SYSLOG-TLS + + This profile defines the transmission of audit trail messages. Transport Layer Security (TLS) Transport Mapping for Syslog (RFC5425) provides the mechanisms for reliable transport, buffering, acknowledgement, authentication, identification, and encryption. The RFC5424 states that the TLS used MUST be TLS version 1.2. For this DICOM profile TLS MUST be used, and version 1.2 or later is RECOMMENDED. + + The words MUST and RECOMMENDED are used in accordance with the IETF specification for normative requirements. + + Any implementation that claims conformance to this profile shall also conform to the Audit Trail Message Format Profile. XML audit trail messages created using the format defined in Audit Trail Message Format Profile shall be transmitted to a collection point using the syslog over TLS mechanism, defined in RFC5425. Systems that comply with this profile shall support message sizes of at least 32768 octets. + + + + Audit messages for other purposes may also be transferred on the same syslog connection. These messages might not conform to the Audit Trail Message Format. + + + RFC5425 specifies mandatory support for 2KB messages, strongly recommends support for at least 8KB, and does not restrict the maximum size. + + + When a received message is longer than the receiving application supports, the message might be discarded or truncated. The sending application will not be notified. + + + + The XML audit trail message shall be inserted into the MSG portion of the SYSLOG-MSG element of the syslog message as defined in RFC5424 "The Syslog Protocol".The XML audit message may contain Unicode characters that are encoded using the UTF-8 encoding rules. + + UTF-8 avoids utilizing the control characters that are reserved by the syslog protocol, but a system that is not prepared for UTF-8 may not be able to display these messages correctly. + + The PRI field shall be set using the facility value of 10 (security/authorization messages). Most messages should have the severity value of 5 (normal but significant), although applications may choose other values if that is appropriate to the more detailed information in the audit message. This means that for most audit messages the PRI field will contain the value "<85>". + The MSGID field in the HEADER of the SYSLOG-MSG shall be set. The value "DICOM+RFC3881" may be used for messages that comply with this profile. + The MSG field of the SYSLOG-MSG shall be present and shall be an XML structure following the DICOM Audit Message Schema (see ). + The syslog message shall be created and transmitted as described in RFC5424. + Any implementation that claims conformance to this Security Profile shall describe in its conformance statement: + + +any configuration parameters relevant to RFC5424 and RFC5425. + + +Any STRUCTURED-DATA that is generated or processed. + + +Any implementation schema or message element extensions for the audit messages. + + +The maximum size of messages that can be sent or received. + + +
+
+ Audit Trail Message Transmission Profile - SYSLOG-UDP + + This profile defines the transmission of audit trail messages. Transmission of Syslog Messages over UDP (RFC5426) provides the mechanisms for rapid transport of audit messages. It is the standardized successor to the informative standard "The BSD syslog protocol (RFC3164) ", which is widely used in a variety of settings. + The syslog port number shall be configurable, with the port number (514) as the default. + The underlying UDP transport might not accept messages longer than the MTU size minus the UDP header length. This may result in longer syslog messages being truncated. When these messages are truncated the resulting XML may be incorrect. Because of this potential for truncated messages and other security concerns, the transmission of syslog messages over TLS may be preferred (see ). + The PRI field shall be set using the facility value of 10 (security/authorization messages). Most messages should have the severity value of 5 (normal but significant), although applications may choose values of 4 (warning condition) if that is appropriate to the more detailed information in the audit message. This means that for most audit messages the PRI field will contain the value "<85>". Audit repositories shall be prepared to deal appropriately with any incoming PRI value. + The MSGID field in the HEADER of the SYSLOG-MSG shall be set. The value "DICOM+RFC3881" may be used for messages that comply with this profile. + The MSG field of the SYSLOG-MSG shall be present and shall be an XML structure following the DICOM Audit Message Schema (see ). + The syslog message shall be created and transmitted as described in RFC5424. + Any implementation that claims conformance to this Security Profile shall describe in its conformance statement: + + +any configuration parameters relevant to RFC5424 and RFC5426. + + +Any STRUCTURED-DATA that is generated or processed. + + +Any implementation schema or message element extensions for the audit messages. + + +The maximum size of messages that can be sent or received. + + +
+
+ + Secure Transport Connection Profiles (Normative) + +
+ The Basic TLS Secure Transport Connection Profile + + An implementation that supports the Basic TLS Secure Transport Connection Profile shall utilize the framework and negotiation mechanism specified by the Transport Layer Security Version 1.0 protocol. specifies mechanisms that shall be supported if the corresponding features within TLS are supported by the Application Entity. The profile does not require the implementation to support all of the features (entity authentication, encryption, integrity checks) of TLS. Other mechanisms may also be used if agreed to by negotiation during establishment of the TLS channel. + + + + + + + + + + + + + + + + + + + + + + + + + + +
Minimum Mechanisms for TLS Features
+ Supported TLS Feature + + Minimum Mechanism +
+ Entity Authentication + + RSA based certificates +
+ Exchange of Master Secrets + + RSA +
+ Data Integrity + + SHA +
+ Privacy + + Triple DES EDE, CBC +
+ IP ports on which an implementation accepts TLS connections, or the mechanism by which this port number is selected or configured, shall be specified in the Conformance Statement. This port shall be different from ports used for other types of transport connections (secure or unsecure). + + It is strongly recommended that systems supporting the Basic TLS Secure Transport Connection Profile use as their port the registered port number "2762 dicom-tls" for the DICOM Upper Layer Protocol on TLS: (decimal). + + The Conformance Statement shall also indicate what mechanisms the implementation supports for Key Management. + The profile does not specify how a TLS Secure Transport Connection is established, or the significance of any certificates exchanged during peer entity authentication. These issues are left up to the Application Entity, which presumably is following some site specified security policy. The identities of the certificate owners can by used by the application entity for audit log support, or to restrict access based on some external access rights control framework. Once the Application Entity has established a Secure Transport Connection, then an Upper Layer Association can use that secure channel. + + There may be an interaction between PDU size and TLS Record size that impacts efficiency of transport. The maximum allowed TLS record size is smaller than the maximum allowed PDU size. + + When an integrity check fails, the connection shall be dropped per the TLS protocol, causing both the sender and the receiver to issue an A-P-ABORT indication to the upper layers with an implementation-specific provider reason. The provider reason used shall be documented in the conformance statement. + + An integrity check failure indicates that the security of the channel may have been compromised. + +
+
+ ISCL Secure Transport Connection Profile + + An implementation that supports the ISCL Transport Connection Profile shall utilize the framework and negotiation mechanism specified by the Integrated Secure Communication Layer, V1.00. An Application Entity shall use ISCL to select the mechanisms specified in . An Application Entity shall as a minimum use an Entity Authentication mechanism and Data Integrity checks. An Application Entity may optionally use a privacy mechanism. + + + + + + + + + + + + + + + + + + + + + + +
Minimum Mechanisms for ISCL Features
+ Supported ISCL Feature + + Minimum Mechanism +
+ Entity Authentication + + Three pass (four-way) authentication(ISO/IEC 9798-2) +
+ Data Integrity + + Either MD-5 encrypted with DES,or DES-MAC (ISO 8730) +
+ Privacy + + DES (see Note) +
+ + The use of DES for privacy is optional for Online Electronic Storage. + + For the Data Integrity check, an implementation may either encrypt the random number before applying MD-5, or encrypt the output of MD-5. The order is specified in the protocol. A receiver shall be able to perform the integrity check on messages regardless of the order. + IP ports on which an implementation accepts ISCL connections, or the mechanism by which this port number is selected or configured, shall be specified in the Conformance Statement. This port shall be different from ports used for other types of transport connections (secure or unsecure). + + It is strongly recommended that systems supporting the ISCL Secure Transport Connection Profile use as their port the registered port number "2761 dicom-iscl" for the DICOM Upper Layer Protocol on ISCL. + + The Conformance Statement shall also indicate what mechanisms the implementation supports for Key Management. + The profile does not specify how an ISCL Secure Transport Connection is established. This issue is left up to the Application Entity, which presumably is following some site specified security policy. Once the Application Entity has established a Secure Transport Connection, then an Upper Layer Association can use that secure channel. + + There may be an interaction between PDU size and ISCL record size that impacts efficiency of transport. + + When an integrity check fails, the connection shall be dropped, per the ISCL protocol, causing both the sender and the receiver to issue an A-P-ABORT indication to the upper layers with an implementation-specific provider reason. The provider reason used shall be documented in the conformance statement. + + An integrity check failure indicates that the security of the channel may have been compromised. + +
+
+ The AES TLS Secure Transport Connection Profile + + An implementation that supports the AES TLS Secure Transport Connection Profile shall utilize the framework and negotiation mechanism specified by the Transport Layer Security Version 1.0 protocol. specifies mechanisms that shall be supported if the corresponding features within TLS are supported by the Application Entity. The profile does not require the implementation to support all of the features (entity authentication, encryption, integrity checks) of TLS. Other mechanisms may also be used if agreed to by negotiation during establishment of the TLS channel. + + + + + + + + + + + + + + +
Minimum Mechanisms for TLS Features
+ Supported TLS Feature + + Minimum Mechanism +
+ Entity Authentication + + RSA based Certificates +
+ Two cyphersuite options shall be offered during TLS negotiation by applications that comply with this profile: + TLS_RSA_WITH_AES_128_CBC_SHA + TLS_RSA_WITH_3DES_EDE_CBC_SHA + The application shall offer both options. The AES version shall be preferred. The fallback to 3DES is offered so that this profile can interoperate easily with applications that only support the 3DES cyphersuite. + IP ports on which an implementation accepts TLS connections, or the mechanism by which this port number is selected or configured, shall be specified in the Conformance Statement. This port shall be different from ports used for other types of transport connections (secure or unsecure). + + It is strongly recommended that systems supporting the AES TLS Secure Transport Connection Profile use as their port the registered port number "2762 dicom-tls" for the DICOM Upper Layer Protocol on TLS: (decimal). + + The Conformance Statement shall also indicate what mechanisms the implementation supports for Key Management. + The profile does not specify how a TLS Secure Transport Connection is established, or the significance of any certificates exchanged during peer entity authentication. These issues are left up to the Application Entity, which presumably is following some site specified security policy. The identities of the certificate owners can by used by the application entity for audit log support, or to restrict access based on some external access rights control framework. Once the Application Entity has established a Secure Transport Connection, then an Upper Layer Association can use that secure channel. + + There may be an interaction between PDU size and TLS Record size that impacts efficiency of transport. The maximum allowed TLS record size is smaller than the maximum allowed PDU size. + + When an integrity check fails, the connection shall be dropped per the TLS protocol, causing both the sender and the receiver to issue an A-P-ABORT indication to the upper layers with an implementation-specific provider reason. The provider reason used shall be documented in the conformance statement. + + An integrity check failure indicates that the security of the channel may have been compromised. + +
+
+ Basic User Identity Association Profile + + An implementation that supports the Basic User Identity Association profile shall accept the User Identity association negotiation sub-item, for User-Identity-Type of 1 or 2. It need not verify the passcode. If a positive response is requested, the implementation shall respond with the association response sub-item. + The user identity from the Primary-field shall be used within the implementation as the user identification. Such uses include recording user identification in audit messages. + + + + + + + + + + + + + + +
Minimum Mechanisms for DICOM Association Negotiation Features - Basic User Identity Association Profile
+ Supported Association Negotiation Feature + + Minimum Mechanism +
+ User Identity + + Username +
+
+
+ User Identity Plus Passcode Association Profile + + An implementation that supports the User Identity plus Passcode Association Profile shall send/accept the User Identity association negotiation sub-item, for User-Identity-Type of 2. If a positive response is requested, the association acceptor implementation shall respond with the association response sub-item. The passcode information shall be made available to internal or external authentication systems. The user identity shall be authenticated by means of the passcode and the authentication system. If the authentication fails, the association shall be rejected. + The user identity from the Primary-field shall be used within the implementation as the user identification. Such uses include recording user identification in audit messages. + + + + + + + + + + + + + + +
User Identity Plus Passcode Association Profile - Minimum Mechanisms for DICOM Association Negotiation Features
+ Supported Association Negotiation Feature + + Minimum Mechanism +
+ User Identity + + Username and Passcode +
+
+
+ Kerberos Identity Negotiation Association Profile + + An implementation that supports the Kerberos Identity Negotiation Association Profile shall send/accept the User Identity association negotiation sub-item, for User-Identity-Type of 3. If a positive response is requested, the association acceptor implementation shall respond with the association response sub-item containing a Kerberos server ticket. The Kerberos server ticket information shall be made available to internal or external Kerberos authentication systems. The user identity shall be authenticated by means of the Kerberos authentication system. If the authentication fails, the association shall be rejected. + The user identity from the Primary-field shall be used within the implementation as the user identification. Such uses include recording user identification in audit messages. + + + + + + + + + + + + + + +
Kerberos Identity Negotiation Association Profile - Minimum Mechanisms for DICOM Association Negotiation Features
+ Supported Association Negotiation Feature + + Minimum Mechanism +
+ User Identity + + Kerberos +
+
+
+ Generic SAML Assertion Identity Negotiation Association Profile + + An implementation that supports the Generic SAML Assertion Identity Negotiation Association Profile shall send/accept the User Identity association negotiation sub-item, for User-Identity-Type of 4. If a positive response is requested, the association acceptor implementation shall respond with the association response sub-item containing a SAML response. The SAML Assertion information shall be made available to internal or external authentication systems. The user identity shall be authenticated by means of an authentication system that employs SAML Assertions. If the authentication fails, the association shall be rejected. + The user identity from the Primary-field shall be used within the implementation as the user identification. Such uses include recording user identification in audit messages. + + + + + + + + + + + + + + +
Generic SAML Assertion Identity Negotiation Association Profile - Minimum Mechanisms for DICOM Association Negotiation Features
+ Supported Association Negotiation Feature + + Minimum Mechanism +
+ User Identity + + SAML Assertion +
+
+
+ Secure Use of Email Transport + + When a DICOM File Set is sent over Email transport in compliance with this profile the following rules shall be followed: + + +The File Set shall be an attachment to the email body. + + +The entire email (body, File Set attachment, and any other attachments) shall be encrypted using AES, in accordance with RFC3851 and RFC3853. + + +The email body and attachments may be compressed in accordance with RFC3851. + + +The email shall be digitally signed by the sender. The signing may be applied before or after encryption. This digital signature shall be interpreted to mean that the sender is attesting to his authorization to disclose the information in this email to the recipient. + + + The email signature is present to provide minimum sender information and to confirm the integrity of the email transmission (body contents, attachment, etc.). The email signature is separate from other signatures that may be present in DICOM reports and objects contained in the File set attached to the email. Those signatures are defined in terms of clinical uses. Any clinical content attestations shall be encoded as digital signatures in the DICOM SOP instances, not as the email signature. The email may be composed by someone who cannot make clinical attestations. Through the use of the email signature, the composer attests that he or she is authorized to transmit the data to the recipient. + + + + This profile is separate from the underlying use of ZIP File or other File Set packaging over email. + + + Where private information is being conveyed, most country regulations require the use of encryption or equivalent protections. This Profile meets the most common requirements of regulations, but there may be additional local requirements. Additional requirements may include mandatory statements in the email body and prohibitions on contents of the email body to protect patient privacy. + + + + +
+
+ + Digital Signature Profiles (Normative) + +
+ Base RSA Digital Signature Profile + + The Base RSA Digital Signature Profile outlines the use of RSA encryption of a MAC to generate a Digital Signature. This Profile does not specify any particular set of Data Elements to sign. Other Digital Signature profiles may refer to this profile, adding specifications of which Data Elements to sign or other customizations. + The creator of a digital signature shall use one of the RIPEMD-160, MD5, SHA-1 or SHA-2 family (SHA256, SHA384, SHA512) of hashing functions to generate a MAC, which is then encrypted using a private RSA key. All validators of digital signatures shall be capable of using a MAC generated by any of the hashing functions specified (RIPEMD-160, MD5, SHA-1 or SHA256, SHA384, SHA512). + + The use of MD5 is not recommended by its inventors, RSA. See:ftp://ftp.rsasecurity.com/pub/pdfs/bulletn4.pdf + + The MAC to be signed shall be padded to a block size matching the RSA key size, as directed in RFC2437 (PKCS #1). The Value of MAC Algorithm (0400,0015) shall be set to either "RIPEMD160", "MD5", "SHA1", "SHA256", "SHA384" or "SHA512". The public key associated with the private key as well as the identity of the Application Entity or equipment manufacturer that owns the RSA key pair shall be transmitted in an X.509 (1993) signature certificate. The Value of the Certificate Type (0400,0110) Attribute shall be set to "X509_1993_SIG". A site-specific policy determines how the X.509 certificates are generated, authenticated, and distributed. A site may issue and distribute X.509 certificates directly, may utilize the services of a Certificate Authority, or use any reasonable method for certificate generation and verification. + If an implementation utilizes timestamps, it shall use a Certified Timestamp Type (0400,0305) of "CMS_TSP". The Certified Timestamp (0400,0310) shall be generated as described in "Internet X.509 Public Key Infrastructure; Time Stamp Protocols; March 2000". +
+
+ Creator RSA Digital Signature Profile + + The creator of a DICOM SOP Instance may generate signatures using the Creator RSA Digital Signature Profile. The Digital Signature produced by this Profile serves as a lifetime data integrity check that can be used to verify that the pixel data in the SOP instance has not been altered since its initial creation. An implementation that supports the Creator RSA Digital Signature Profile may include a Creator RSA Digital Signature with every SOP Instance that it creates; however, the implementation is not required to do so. + As a minimum, an implementation shall include the following attributes in generating the Creator RSA Digital Signature: + + + the SOP Class and Instance UIDs + + + the SOP Creation Date and Time, if present + + + the Study and Series Instance UIDs + + + any attributes of the General Equipment Module that are present + + + any attributes of the Overlay Plane Module, Curve Module or Graphic Annotation Module that are present + + + any attributes of the General Image Module and Image Pixel Module that are present + + + any attributes of the SR Document General Module and SR Document Content Module that are present + + + any attributes of the Waveform Module and Waveform Annotation Module that are present + + + any attributes of the Multi-frame Functional Groups Module that are present + + + any attributes of the Enhanced MR Image Module that are present + + + any attributes of the MR Spectroscopy Module that are present + + + any attributes of the Raw Data Module that are present + + + any attributes of the Enhanced CT Image Module that are present + + + any attributes of the Enhanced XA/XRF Image Module that are present + + + any attributes of the Segmentation Image Module that are present + + + any attributes of the Encapsulated Document Module that are present + + + any attributes of the X-Ray 3D Image Module that are present + + + any attributes of the Enhanced PET Image Module that are present + + + any attributes of the Enhanced US Image Module that are present + + + any attributes of the Surface Segmentation Module that are present + + + any attributes of the Surface Mesh Module that are present + + + any attributes of the Structured Display Module, Structured Display Annotation Module, and Structured Display Image Box Module that are present + + + any Attributes of the Implant Template Module that are present + + + any Attributes of the Implant Assembly Template Module that are present + + + any Attributes of the Implant Template Group Module that are present + + + any attributes of the Point Cloud Module that are present + + + any attributes of the Enhanced Mammography Image Module that are present + + + any attributes of the Tractography Results Module that are present + + + any attributes of the Volumetric Graphic Annotation Module that are present + + + + The Digital Signature shall be created using the methodology described in the Base RSA Digital Signature Profile. Typically the certificate and associated private key used to produce Creator RSA Digital Signatures are configuration parameters of the Application Entity set by service or installation engineers. + Creator RSA Digital Signatures bear no direct relationship to other Digital Signatures. However, other Digital Signatures, such as the Authorization Digital Signature, may be used to collaborate the timestamp of a Creator RSA Digital Signature. +
+
+ Authorization RSA Digital Signature Profile + + The technician or physician who approves a DICOM SOP Instance for use may request the Application Entity to generate a signature using the Authorization RSA Digital Signature Profile. The Digital Signature produced serves as a lifetime data integrity check that can be used to verify that the pixel data in the SOP instance is the same that the technician or physician saw when they made the approval. + As a minimum, an implementation shall include the following attributes in generating the Authorization RSA Digital Signature: + + + the SOP Class and Instance UIDs + + + the Study and Series Instance UIDs + + + any attributes whose Values are verifiable by the technician or physician (e.g., their Values are displayed to the technician or physician) + + + any attributes of the Overlay Plane, Curve or Graphic Annotation modules that are present + + + any attributes of the General Image and Image Pixel modules that are present + + + any attributes of the SR Document General and SR Document Content modules that are present + + + any attributes of the Waveform and Waveform Annotation modules that are present + + + any attributes of the Multi-frame Functional Groups module that are present + + + any attributes of the Enhanced MR Image module that are present + + + any attributes of the MR Spectroscopy modules that are present + + + any attributes of the Raw Data module that are present + + + any attributes of the Enhanced CT Image module that are present + + + any attributes of the Enhanced XA/XRF Image module that are present + + + any attributes of the Segmentation Image module that are present + + + any attributes of the Encapsulated Document module that are present + + + any attributes of the X-Ray 3D Image module that are present + + + any attributes of the Enhanced PET Image module that are present + + + any attributes of the Enhanced US Image module that are present + + + any attributes of the Surface Segmentation module that are present + + + any attributes of the Surface Mesh Module that are present + + + any attributes of the Structured Display, Structured Display Annotation, and Structured Display Image Box modules that are present + + + any Attributes of the Implant Template module that are present + + + any Attributes of the Implant Assembly Template module that are present + + + any Attributes of the Implant Template Group module that are present + + + any attributes of the Point Cloud Module that are present + + + any attributes of the Enhanced Mammography Image module that are present + + + any attributes of the Volumetric Graphic Annotation Module that are present + + + The Digital Signature shall be created using the methodology described in the Base RSA Digital Signature Profile. The Application Entity shall determine the identity of the technician or physician and obtain their certificate through a site-specific procedure such as a login mechanism or a smart card. + Authorization RSA Digital Signatures bear no direct relationship to other Digital Signatures. However, other Digital Signatures, such as the Creator RSA Digital Signature, may be used to collaborate the timestamp of an Authorization RSA Digital Signature. +
+
+ Structured Report RSA Digital Signature Profile + + This profile defines a mechanism for adding Digital Signatures to Structured Reports or Key Object Selection Documents where there is no more than one Verifying Observer. Instances that follow this Digital Signature Profile shall include at least one Digital Signature at the top level of the Data Set. + All Digital Signatures that follow this profile shall include a Digital Signature Purpose Code Sequence Attribute (0400,0401). + As a minimum, an implementation shall include the following attributes in generating the Digital Signature required by this profile: + + + the SOP Class UID + + + the Study and Series Instance UIDs + + + all attributes of the General Equipment Module that are present + + + the Current Requested Procedure Evidence Sequence + + + the Pertinent Other Evidence Sequence + + + the Predecessor Documents Sequence + + + the Observation DateTime + + + all attributes of the SR Document Content Module that are present + + + If the Verification Flag is set to "VERIFIED" (and the SOP Instance UID can no longer change) at least one of the Digital Signatures profile shall have the purpose of (5,ASTM-sigpurpose,"Verification Signature") and shall also include the following Attributes in addition to the above attributes: + + + the SOP Instance UID + + + the Verification Flag + + + the Verifying Observer Sequence + + + the Verification DateTime + + + + The system may also add a Creator RSA Digital Signature, which could cover other attributes that the machine can verify. + + All occurrences of Referenced SOP Instance MAC Sequence (0400,0403) shall have the Value of MAC Algorithm (0400,0015) set to either "RIPEMD160", "MD5", "SHA1", "SHA256", "SHA384" or "SHA512".. + The Digital Signature shall be created using the methodology described in the Base RSA Digital Signature Profile. The Application Entity shall determine the identity of the signatories and obtain their certificate through an application-specific procedure such as a login mechanism or a smart card. The conformance statement shall specify how the application identifies signatories and obtains certificates. + + Structured Report RSA Digital Signatures bear no direct relationship to other Digital Signatures. However, other Digital Signatures, such as the Creator RSA Digital Signature, may be used to corroborate the timestamp of a Structured Report RSA Digital Signature. + + +
+
+ + Media Storage Security Profiles (Normative) + +
+ Basic DICOM Media Security Profile + + The Basic DICOM Media Security Profile allows encapsulation of a DICOM File into a Secure DICOM File such that the following aspects of security are addressed: + + +confidentiality, + + +integrity, + + +data origin authentication (optional). + + +This profile specifies the use of either AES or Triple-DES for content encryption and RSA, or password-based encryption and AES or Triple-DES, for the key transport of the content-encryption keys. The encrypted content is a DICOM File that can either + + +be signed with one or more digital signatures, using SHA-1, SHA256, SHA384, or SHA512 as the digest algorithm and RSA as the signature algorithm, or + + +be digested with SHA-1, SHA256, SHA384, or SHA512 as digest algorithm, without application of digital signatures. + + + + The digest algorithm requirements will evolve as the threats evolve. As the digest requirements have changed, this profile has changed to include additional requirements. + +
+ Encapsulation of A DICOM File in a Secure DICOM File + + A Secure DICOM File conforming to this security profile shall contain an Enveloped-data content type of the Cryptographic Message Syntax defined in RFC3852, RFC3370 and RFC3565. The enveloped data shall use RSA [RFC3447], or password-based encryption using PBKDF2 [RFC2898] for the key derivation algorithm and either AES or Triple-DES [RFC3211], for the key transport of the content-encryption keys. Creators of a Secure DICOM File conforming to this security profile may use either AES or Triple-DES for content-encryption. Readers claiming conformance to this profile shall be capable of decrypting Secure DICOM Files using either AES or Triple-DES. The AES key length may be any length allowed by the RFCs. The Triple-DES key length is 168 bits as defined by ANSI X9.52. Encoding shall be performed according to the specifications for RSA Key Transport and Triple DES Content Encryption in RFC3370, and for AES Content Encryption in RFC3565. + The encrypted content of the Enveloped-data content type shall be of the following choices: + + +Signed-data content type; + + +Digested-data content type. + + +In both cases, SHA-1 [SHA-1], SHA256, SHA384, or SHA512 [SHA-2] shall be used as the digest algorithm. In case of the Signed-data content type, RSA [RFC2313] shall be used as the signature algorithm. + In the case of password-based encryption using PBKDF2, the octet string that contains the password used to generate the key shall be limited to the encoding and the graphic character representation defined by the Default Character Repertoire. + + + + RSA key transport of the content-encryption keys is specified as a requirement in the European Prestandard ENV 13608-2: Health Informatics - Security for healthcare communication - Part 2: Secure data objects. + + + No requirements on the size of the asymmetric key pairs used for RSA key transport are defined in this profile. + + + No requirements or restrictions on the use of the SignedAttributes element of the Signed-data content type's SignerInfo structure are defined in this profile. SignedAttributes might for example be used to specify the signing time or SMIME capabilities, as required by ENV 13608-2. + + + The use of password-based encryption for key transport of content encryption keys is potentially less secure than certificate-based encryption, but may be useful when the list of recipients is not known a priori or when there is no public key infrastructure deployed. The security depends on the entropy of the password, which if user-selected can be quite low. RFC3211 strongly recommends the use of a pass "phrase" rather than a single word, and RFC2898 does not impose any practical length limit. Also, the method used to exchange the password or pass phrase also could have a significant impact on the level of security. + + + PBKDF2 as defined in RFC2898 specifies the password to be "an octet string of arbitrary length whose interpretation as a text string is unspecified". For interoperability between the sender and recipient, both a character encoding scheme and a graphic character representation needs to be defined. ISO IR6 (US-ASCII), being the Default Character Repertoire for DICOM (see ), is specified in order to avoid any potential ambiguity caused by the use of other character sets (such as UTF-8) that do not necessarily result in the same binary values for particular graphic character representation. + + + + The graphic character representation of certain symbols in ISO IR6 is explicitly defined, even though the same binary representation may have a different graphic character representation in other 7-bit schemes. For example, in the version of ISO 646 used in Japan (ISO-IR 14 Romaji), 05/12 is represented as "¥" rather than backslash "\". It is the responsibility of the application to assure that the input method and display of such symbols to the user is mapped to the correct encoding, regardless of locale. I.e., if the password is "123\$", then it should be encoded as 03/01 03/02 03/03 05/12 02/04, regardless of whether the user types the backslash "\"(U+005C) on a Japanese or US keyboard; they should not be expected to type the "¥" (U+00A5) key on a Japanese keyboard, nor should 05/12 be displayed as "¥" if the password is displayed as text. + The restriction to the ISO IR 6 encoding and graphic character representation (rather than, for example, the minimal encoding of UTF-8) also eliminates the ambiguity introduced by homographs (characters that look the same but encode differently), and alternative encodings with the same meaning, such as the single German character "ß" (U+00DF) as opposed to the two-character "ss" (U+0073 U+0073), and the use of phonetic as opposed to ideographic representation of the same meaning, such as Japanese hiragana "ぞう" (U+305E U+3046) versus kanji "像" (U+50CF). + It is the responsibility of the application to prevent the user from creating passwords using characters that cannot be represented; e.g., on a Western European keyboard, the user should not be permitted to enter an accented character such as "é" (U+00E9) or "ö" (U+00F6), since there is no defined mapping of such characters to IS IR 6 characters (such as "e" or "o"). + +
+
+
+ + Attribute Confidentiality Profiles + + This Annex describes Profiles and Options to address the removal and replacement of Attributes within a DICOM Dataset that may potentially result in leakage of Individually Identifiable Information (III) about the patient or other individuals or organizations involved in acquisition. + Profiles are provided to address the balance between the removal of information and the need to retain information so that the Datasets remain useful for their intended purpose. + Options are used in addition to profiles to prevent a combinatorial expansion of different Profiles. +
+ Application Level Confidentiality Profiles + + Application Level Confidentiality Profiles address the following aspects of security: + + +Data Confidentiality at the application layer. + + +Other aspects of security not addressed by these profiles, that may be addressed elsewhere in the standard include: + + +Confidentiality in other layers of the DICOM model; + + +Data Integrity. + + +These Profiles are targeted toward creating a special purpose, de-identified version of an already-existing Data Set. It is not intended to replace the original SOP Instance from which the de-identified SOP Instance is created, nor is it intended to act as the primary representation of clinical Data Sets in image archives. The de-identified SOP Instances are useful, for example, in creating teaching or research files, performing clinical trials, or submission to registries where the identity of the patient and other individuals is required to be protected. In some cases, it is also necessary to provide a means of recovering identity by authorized personnel. +
+ De-identifier + + An Application may claim conformance to an Application Level Confidentiality Profile and Options as a de-identifier if it protects and retains + allAttributes as specified in the Profile and Options. Protection in this context is defined as the following process: + + + The application may create one or more instances of the Encrypted Attributes Data Set and copy Attributes to be protected into the (single) item of the Modified Attributes Sequence (0400,0550) of one or more of the Encrypted Attributes Data Set instances. + + + +A complete reconstruction of the original Data Set may not be possible; however, Attributes (e.g., SOP Instance UID) in the Modified Attributes Sequence of an Encrypted Attributes Data Set may refer back to the original SOP Instance holding the original Data Set. + + +It is not required that the Encrypted Attributes Data Set be created; indeed, there may be circumstances where the Dataset is expected to be archived long enough that any contemporary encryption technology may be inadequate to provide long term protection against unauthorized recovery of identification. + + +Other mechanisms to assist in identity recovery or longitudinal consistency of replaced UIDs or dates and times are deprecated in favor of the Encrypted Attributes Data Set mechanism that is intended for this purpose. For example, if it is desired to include an encrypted hash of the Patient's Name, it should not be encoded in a separate private attribute implemented for that purpose, but should be included in the Encrypted Attributes Data Set and encoded using the standard mechanism. This allows for compatibility between different implementations and provides security based on the quality and control of the encryption keys. Note also, that unencrypted hashes are considerably less secure and should be avoided, since they are vulnerable to trivial dictionary based attacks. + + + + + + Each Attribute to be protected shall then either be removed from the dataset, or have its value replaced by a different "replacement value" that does not allow identification of the patient. + + + +It is the responsibility of the de-identifier to ensure that this process does not negatively affect the integrity of the Information Object Definition, i. e. Dummy values may be necessary for Type 1 Attributes that are protected but may not be sent with zero length, and are to be stored or exchanged in encrypted form by applications that may not be aware of the security mechanism. + + +The standard does not mandate the use of any particular dummy value, and indeed it may have some meaning, for example in a data set that may be used for teaching purposes, where the real patient identifying information is encrypted for later retrieval, but a meaningful alternative form of identification is provided. For example, a dummy Patient's Name (0010,0010) may convey the type of pathology in a teaching case. It is the responsibility of the de-identifier software or human operator to ensure that the dummy values cannot be used to identify the patient. + + +It is the responsibility of the de-identifier to ensure the consistency of dummy values for Attributes such as Study Instance UID (0020,000D) or Frame of Reference UID (0020,0052) if multiple related SOP Instances are protected. Indeed, all Attributes of every entity about the Instance level should remain consistent for all Instances protected, e.g., Patient ID for the Patient entity, Study ID for the Study entity, Series Number for the Series entity. + + +Some profiles do not allow selective protection of parts of a Sequence of Items. If an Attribute to be protected is contained in a Sequence of Items, the complete Sequence of Items may need to be protected. + + +The de-identifier should ensure that no identifying information that is burned in to the image pixel data either because the modality does not generate such burned in identification in the first place, or by removing it through the use of the Clean Pixel Data Option; see . If non-pixel data graphics or overlays contain identification, the de-identifier is required to remove them, or clean them if the Clean Graphics option is supported. See The means by which burned in or graphic identifying information is located and removed is outside the scope of this standard. + + + + + + Each Attribute specified to be retained shall be retained. At the discretion of the de-identifier, Attributes may be added to the dataset to be protected. + +As an example, the Attribute Patient's Age (0010,1010) might be introduced as a replacement for Patient's Birth Date (0010,0030) if the patient's age is of importance, and the profile permits it. + + + + If used, all instances of the Encrypted Attributes Data Set shall be encoded with a DICOM Transfer Syntax, encrypted, and stored in the dataset to be protected as an Item of the Encrypted Attributes Sequence (0400,0500). The encryption shall be done using RSA [RFC2313] for the key transport of the content-encryption keys. A de-identifier conforming to this security profile may use either AES or Triple-DES for content-encryption. The AES key length may be any length allowed by the RFCs. The Triple-DES key length is 168 bits as defined by ANSI X9.52. Encoding shall be performed according to the specifications for RSA Key Transport and Triple DES Content Encryption in RFC3370 and for AES Content Encryption in RFC3565. + + + +Each item of the Encrypted Attributes Sequence (0400,0500) consists of two Attributes, Encrypted Content Transfer Syntax UID (0400,0510) containing the UID of the Transfer Syntax that was used to encode the instance of the Encrypted Attributes Data Set, and Encrypted Content (0400,0520) containing the block of data resulting from the encryption of the Encrypted Attributes Data Set instance. + + +RSA key transport of the content-encryption keys is specified as a requirement in the European Prestandard ENV 13608-2: Health Informatics - Security for healthcare communication - Part 2: Secure data objects. + + + + + + No requirements on the size of the asymmetric key pairs used for RSA key transport are defined in this confidentiality scheme. Implementations claiming conformance to the Basic Application Level Confidentiality Profile as a de-identifier shall always protect (e.g., encrypt and replace) the SOP Instance UID (0008,0018) Attribute as well as all references to other SOP Instances, whether contained in the main dataset or embedded in an Item of a Sequence of Items, that could potentially be used by unauthorized entities to identify the patient. + +In the case of a SOP Instance UID embedded in an item of a sequence, this means that the enclosing Attribute in the top-level data set must be encrypted in its entirety. + + + + The attribute Patient Identity Removed (0012,0062) shall be replaced or added to the dataset with a value of YES, and one or more codes from corresponding to the profile and options used shall be added to De-identification Method Code Sequence (0012,0064). A text string describing the method used may also be inserted in or added to De-identification Method (0012,0063), but is not required. + + + If the Dataset being de-identified is being stored within a DICOM File, then the File Meta Information including the 128 byte preamble, if present, shall be replaced with a description of the de-identifying application. Otherwise, there is a risk that identity information may leak through unmodified File Meta Information or preamble. See . + + + + + The Attributes listed in for each profile are contained in Standard IODs, or may be contained in Standard Extended IODs. An implementation claiming conformance to an Application Level Confidentiality Profile as a de-identifier shall protect or retain all instances of the Attributes listed in , whether contained in the main dataset or embedded in an Item of a Sequence of Items. The following action codes are used in the table: + + +D - replace with a non-zero length value that may be a dummy value and consistent with the VR + + +Z - replace with a zero length value, or a non-zero length value that may be a dummy value and consistent with the VR + + +X - remove + + +K - keep (unchanged for non-sequence attributes, cleaned for sequences) + + +C - clean, that is replace with values of similar meaning known not to contain identifying information and consistent with the VR + + +U - replace with a non-zero length UID that is internally consistent within a set of Instances + + +Z/D - Z unless D is required to maintain IOD conformance (Type 2 versus Type 1) + + +X/Z - X unless Z is required to maintain IOD conformance (Type 3 versus Type 2) + + +X/D - X unless D is required to maintain IOD conformance (Type 3 versus Type 1) + + +X/Z/D - X unless Z or D is required to maintain IOD conformance (Type 3 versus Type 2 versus Type 1) + + +X/Z/U* - X unless Z or replacement of contained instance UIDs (U) is required to maintain IOD conformance (Type 3 versus Type 2 versus Type 1 sequences containing UID references) + + +These action codes are applicable to both Sequence and non-Sequence attributes; in the case of Sequences, the action is applicable to the Sequence and all of its contents. Cleaning a sequence ("C" action) may entail either changing values of attributes within that Sequence when the meaning of the Sequence within the context of its use in the IOD is understood, or recursively applying the profile rules to each Dataset in each Item of the Sequence. Keeping a Sequence ("K" action) requires recursively applying the profile rules to each Dataset in each Item of the Sequence (for example, in order to remap any UIDs contained within that sequence). + A requirement for an Option, when implemented, overrides any requirement for the underlying Profile. + + + + The Attributes listed in may not be sufficient to guarantee confidentiality of patient identity. In particular, identifying information may be contained in Private Attributes, new Standard Attributes, Retired Standard Attributes and additional Standard Attributes not present in Standard Composite IODs (as defined in ) but used in Standard Extended SOP Classes. indicates those Attributes that are used in Standard Composite IODs as well as those Attributes that are Retired. Also included in are some Elements that are not normally found in a Dataset, but are used in Commands, Directories and Meta Information Headers, but that could be misused within Private Sequences. Textual Content Items of Structured Reports, textual annotations of Presentation States, Curves and Overlays are specifically addressed. It is the responsibility of the de-identifier to ensure that all identifying information is removed. + + + It should be noted that conformance to an Application Level Confidentiality Profile does not necessarily guarantee confidentiality. For example, if an attacker already has access to the original images, the Pixel Data could be matched, though the probability and impact of such a threat may be deemed to be negligible. If the Encrypted Attributes Sequence is used, it should be understood that any encryption scheme may be vulnerable to attack. Also, an organization's Security Policy and Key Management policy are recognized to have a much greater impact on the effectiveness of protection. + + + National and local regulations, which may vary, might require that additional attributes be de-identified, though the Profiles and Options have been designed to be sufficient to satisfy known regulations without compromising the usefulness of the de-identified instances for their intended purpose. + + + + is normative, but it is subject to extension as the DICOM Standard evolves and other similar Attributes are added to IODs. De-identifiers may take this extensibility into account, for example, by considering handling all dates and times on the basis of their Value Representation of DT, DA or TM, rather than just those date and time Attributes lists. + + + The Profiles and Options do not specify whether the design of a de-identifier should be to remove what is know to be a risk of identity leakage, or to retain only what is known to be safe. The former approach may fail when the standard is extended, or when a vendor adds unanticipated standard or private attributes, whilst the latter requires an extensive, if not complete, comparison of each instance with the Information Object Definitions in to avoid discarding required or useful information. defines the minimum actions required for conformance. + + + De-identification of Private SOP Classes is not defined. + + + The "C" (clean) action is specified not only for string VRs, but also for Code Sequences, since the use of private or local codes and non-standard code meanings may potentially cause identity leakage. + + + The Digital Signatures Sequences needs to be removed because it contains the certificate of the signer; theoretically the signature could be verified and the object re-signed by the de-identifier itself with its own certificate, but this is not required by the Standard. + + + In general, there are no CS VR Attributes in this table, since it is usually safe to assume that code strings do not contain identifying information. + + + In general, there are no Code Sequence Attributes in this table, since it is usually safe to assume that coded sequence entries, including private codes, do not contain identifying information. Exceptions are codes for providers and staff. + + + The Clean Pixel Data and Clean Recognizable Visual Features Options are not listed in this table, since they are defined by descriptions of operations on the Pixel Data itself. The Clean Pixel Data option may be applied to the Pixel Data within the Icon Image Sequence, or more likely the Icon Image Sequence may be recreated entirely once the Pixel Data of the main Dataset has been cleaned. The Icon Image Sequence is to be removed when its Pixel Data cannot be cleaned. + + + The Original Attributes Sequence (0400,0561) (which in turn contains the Modified Attributes Sequence (0400,0550) ) generally needs to be removed, because it may contain unencrypted copies of other Attributes that may have been modified (e.g., coerced to use local identifiers and names during import of foreign images); an alternative approach would be to selectively modify its contents. This is distinct from the use of the Modified Attributes Sequence (0400,0550) within the Encrypted Attributes Sequence (0400,0500). + + + + distinguishes Attributes that are in standard Composite IODs defined in from those that are not; some Attributes are defined in for other IODs, or have a specific usage other than in the top level Dataset of a Composite IOD, but are (mis-) used by implementers in instances as a Standard Extended SOP Class at other levels than as defined by the Standard. Any such Attributes encountered may be removed without compromising the conformance of the instance with the standard IOD. For example, Verifying Observer Sequence (0040,A073) is only defined in structured report IODs and hence is described in as D since it is Type 1C; if encountered in an image instance, it should simply be removed (treated as X). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Application Level Confidentiality Profile Attributes
+ + Attribute Name + + + + Tag + + + + Retired (from ) + + + + In Std. Comp. IOD (from ) + + + + Basic Profile + + + + Retain Safe Private Option + + + + Retain UIDs Option + + + + Retain Device Ident. Option + + + + Retain Patient Chars. Option + + + + Retain Long. Full Dates Option + + + + Retain Long. Modif. Dates Option + + + + Clean Desc. Option + + + + Clean Struct. Cont. Option + + + + Clean Graph. Option + +
+ Accession Number + + (0008,0050) + + N + + Y + + Z + + + + + + + + + +
+ Acquisition Comments + + (0018,4000) + + Y + + N + + X + + + + + + + + C + + +
+ Acquisition Context Sequence + + (0040,0555) + + N + + Y + + X + + + + + + + + + C + +
+ Acquisition Date + + (0008,0022) + + N + + Y + + X/Z + + + + + + K + + C + + + +
+ Acquisition DateTime + + (0008,002A) + + N + + Y + + X/D + + + + + + K + + C + + + +
+ Acquisition Device Processing Description + + (0018,1400) + + N + + Y + + X/D + + + + + + + + C + + +
+ Acquisition Protocol Description + + (0018,9424) + + N + + Y + + X + + + + + + + + C + + +
+ Acquisition Time + + (0008,0032) + + N + + Y + + X/Z + + + + + + K + + C + + + +
+ Actual Human Performers Sequence + + (0040,4035) + + N + + N + + X + + + + + + + + + +
+ Additional Patient's History + + (0010,21B0) + + N + + Y + + X + + + + + + + + C + + +
+ Address (Trial) + + (0040,A353) + + Y + + N + + X + + + + + + + + + +
+ Admission ID + + (0038,0010) + + N + + Y + + X + + + + + + + + + +
+ Admitting Date + + (0038,0020) + + N + + N + + X + + + + + + K + + C + + + +
+ Admitting Diagnoses Code Sequence + + (0008,1084) + + N + + Y + + X + + + + + + + + C + + +
+ Admitting Diagnoses Description + + (0008,1080) + + N + + Y + + X + + + + + + + + C + + +
+ Admitting Time + + (0038,0021) + + N + + N + + X + + + + + + K + + C + + + +
+ Affected SOP Instance UID + + (0000,1000) + + N + + N + + X + + + K + + + + + + + +
+ Allergies + + (0010,2110) + + N + + N + + X + + + + + C + + + + C + + +
+ Arbitrary + + (4000,0010) + + Y + + N + + X + + + + + + + + + +
+ Author Observer Sequence + + (0040,A078) + + N + + Y + + X + + + + + + + + + +
+ Branch of Service + + (0010,1081) + + N + + N + + X + + + + + + + + + +
+ Cassette ID + + (0018,1007) + + N + + Y + + X + + + + K + + + + + + +
+ Comments on the Performed Procedure Step + + (0040,0280) + + N + + Y + + X + + + + + + + + C + + +
+ Concatenation UID + + (0020,9161) + + N + + Y + + U + + + K + + + + + + + +
+ Confidentiality Constraint on Patient Data Description + + (0040,3001) + + N + + N + + X + + + + + + + + + +
+ Consulting Physician Identification Sequence + + (0008,009D) + + N + + Y + + X + + + + + + + + + +
+ Consulting Physician's Name + + (0008,009C) + + N + + Y + + Z + + + + + + + + + +
+ Content Creator's Name + + (0070,0084) + + N + + Y + + Z + + + + + + + + + +
+ Content Creator's Identification Code Sequence + + (0070,0086) + + N + + Y + + X + + + + + + + + + +
+ Content Date + + (0008,0023) + + N + + Y + + Z/D + + + + + + K + + C + + + +
+ Content Sequence + + (0040,A730) + + N + + Y + + X + + + + + + + + + C + +
+ Content Time + + (0008,0033) + + N + + Y + + Z/D + + + + + + K + + C + + + +
+ Contrast Bolus Agent + + (0018,0010) + + N + + Y + + Z/D + + + + + + + + C + + +
+ Contribution Description + + (0018,A003) + + N + + Y + + X + + + + + + + + C + + +
+ Country of Residence + + (0010,2150) + + N + + N + + X + + + + + + + + + +
+ Current Observer (Trial) + + (0040,A307) + + Y + + N + + X + + + + + + + + + +
+ Current Patient Location + + (0038,0300) + + N + + N + + X + + + + + + + + + +
+ Curve Data + + (50xx,xxxx) + + Y + + N + + X + + + + + + + + + + C +
+ Curve Date + + (0008,0025) + + Y + + Y + + X + + + + + + K + + C + + + +
+ Curve Time + + (0008,0035) + + Y + + Y + + X + + + + + + K + + C + + + +
+ Custodial Organization Sequence + + (0040,A07C) + + N + + Y + + X + + + + + + + + + +
+ Data Set Trailing Padding + + (FFFC,FFFC) + + N + + Y + + X + + + + + + + + + +
+ Derivation Description + + (0008,2111) + + N + + Y + + X + + + + + + + + C + + +
+ Detector ID + + (0018,700A) + + N + + Y + + X/D + + + + K + + + + + + +
+ Device Serial Number + + (0018,1000) + + N + + Y + + X/Z/D + + + + K + + + + + + +
+ Device UID + + (0018,1002) + + N + + Y + + U + + + K + + K + + + + + + +
+ Digital Signature UID + + (0400,0100) + + N + + Y + + X + + + + + + + + + +
+ Digital Signatures Sequence + + (FFFA,FFFA) + + N + + Y + + X + + + + + + + + + +
+ Dimension Organization UID + + (0020,9164) + + N + + Y + + U + + + K + + + + + + + +
+ Discharge Diagnosis Description + + (0038,0040) + + Y + + N + + X + + + + + + + + C + + +
+ Distribution Address + + (4008,011A) + + Y + + N + + X + + + + + + + + + +
+ Distribution Name + + (4008,0119) + + Y + + N + + X + + + + + + + + + +
+ Dose Reference UID + + (300A,0013) + + N + + Y + + U + + + K + + + + + + + +
+ End Acquisition DateTime + + (0018,9517) + + N + + Y + + X/D + + + + + + K + + C + + + +
+ Ethnic Group + + (0010,2160) + + N + + Y + + X + + + + + K + + + + + +
+ Expected Completion DateTime + + (0040,4011) + + N + + N + + X + + + + + + K + + C + + + +
+ Failed SOP Instance UID List + + (0008,0058) + + N + + N + + U + + + K + + + + + + + +
+ Fiducial UID + + (0070,031A) + + N + + Y + + U + + + K + + + + + + + +
+ Filler Order Number / Imaging Service Request + + (0040,2017) + + N + + Y + + Z + + + + + + + + + +
+ Frame Comments + + (0020,9158) + + N + + Y + + X + + + + + + + + C + + +
+ Frame of Reference UID + + (0020,0052) + + N + + Y + + U + + + K + + + + + + + +
+ Gantry ID + + (0018,1008) + + N + + Y + + X + + + + K + + + + + + +
+ Generator ID + + (0018,1005) + + N + + Y + + X + + + + K + + + + + + +
+ Graphic Annotation Sequence + + (0070,0001) + + N + + Y + + D + + + + + + + + + + C +
+ Human Performers Name + + (0040,4037) + + N + + N + + X + + + + + + + + + +
+ Human Performers Organization + + (0040,4036) + + N + + N + + X + + + + + + + + + +
+ Icon Image Sequence(see Note 12) + + (0088,0200) + + N + + Y + + X + + + + + + + + + +
+ Identifying Comments + + (0008,4000) + + Y + + N + + X + + + + + + + + C + + +
+ Image Comments + + (0020,4000) + + N + + Y + + X + + + + + + + + C + + +
+ Image Presentation Comments + + (0028,4000) + + Y + + N + + X + + + + + + + + + +
+ Imaging Service Request Comments + + (0040,2400) + + N + + N + + X + + + + + + + + C + + +
+ Impressions + + (4008,0300) + + Y + + N + + X + + + + + + + + C + + +
+ Instance Coercion DateTime + + (0008,0015) + + N + + Y + + X + + + + + + K + + C + + + +
+ Instance Creator UID + + (0008,0014) + + N + + Y + + U + + + K + + + + + + + +
+ Institution Address + + (0008,0081) + + N + + Y + + X + + + + + + + + + +
+ Institution Code Sequence + + (0008,0082) + + N + + Y + + X/Z/D + + + + + + + + + +
+ Institution Name + + (0008,0080) + + N + + Y + + X/Z/D + + + + + + + + + +
+ Institutional Department Name + + (0008,1040) + + N + + Y + + X + + + + + + + + + +
+ Insurance Plan Identification + + (0010,1050) + + Y + + N + + X + + + + + + + + + +
+ Intended Recipients of Results Identification Sequence + + (0040,1011) + + N + + N + + X + + + + + + + + + +
+ Interpretation Approver Sequence + + (4008,0111) + + Y + + N + + X + + + + + + + + + +
+ Interpretation Author + + (4008,010C) + + Y + + N + + X + + + + + + + + + +
+ Interpretation Diagnosis Description + + (4008,0115) + + Y + + N + + X + + + + + + + + C + + +
+ Interpretation ID Issuer + + (4008,0202) + + Y + + N + + X + + + + + + + + + +
+ Interpretation Recorder + + (4008,0102) + + Y + + N + + X + + + + + + + + + +
+ Interpretation Text + + (4008,010B) + + Y + + N + + X + + + + + + + + C + + +
+ Interpretation Transcriber + + (4008,010A) + + Y + + N + + X + + + + + + + + + +
+ Irradiation Event UID + + (0008,3010) + + N + + Y + + U + + + K + + + + + + + +
+ Issuer of Admission ID + + (0038,0011) + + N + + Y + + X + + + + + + + + + +
+ Issuer of Patient ID + + (0010,0021) + + N + + Y + + X + + + + + + + + + +
+ Issuer of Service Episode ID + + (0038,0061) + + N + + Y + + X + + + + + + + + + +
+ Large Palette Color Lookup Table UID + + (0028,1214) + + Y + + N + + U + + + K + + + + + + + +
+ Last Menstrual Date + + (0010,21D0) + + N + + N + + X + + + + + + K + + C + + + +
+ MAC + + (0400,0404) + + N + + Y + + X + + + + + + + + + +
+ Media Storage SOP Instance UID + + (0002,0003) + + N + + N + + U + + + K + + + + + + + +
+ Medical Alerts + + (0010,2000) + + N + + N + + X + + + + + + + + C + + +
+ Medical Record Locator + + (0010,1090) + + N + + N + + X + + + + + + + + + +
+ Military Rank + + (0010,1080) + + N + + N + + X + + + + + + + + + +
+ Modified Attributes Sequence + + (0400,0550) + + N + + N + + X + + + + + + + + + +
+ Modified Image Description + + (0020,3406) + + Y + + N + + X + + + + + + + + + +
+ Modifying Device ID + + (0020,3401) + + Y + + N + + X + + + + + + + + + +
+ Modifying Device Manufacturer + + (0020,3404) + + Y + + N + + X + + + + + + + + + +
+ Name of Physician(s) Reading Study + + (0008,1060) + + N + + Y + + X + + + + + + + + + +
+ Names of Intended Recipient of Results + + (0040,1010) + + N + + N + + X + + + + + + + + + +
+ Observation Date (Trial) + + (0040,A192) + + Y + + N + + X + + + + + + K + + C + + + +
+ Observation Subject UID (Trial) + + (0040,A402) + + Y + + N + + U + + + K + + + + + + + +
+ Observation Time (Trial) + + (0040,A193) + + Y + + N + + X + + + + + + K + + C + + + +
+ Observation UID + + (0040,A171) + + N + + Y + + U + + + K + + + + + + + +
+ Occupation + + (0010,2180) + + N + + Y + + X + + + + + + + + C + + +
+ Operators' Identification Sequence + + (0008,1072) + + N + + Y + + X/D + + + + + + + + + +
+ Operators' Name + + (0008,1070) + + N + + Y + + X/Z/D + + + + + + + + + +
+ Original Attributes Sequence + + (0400,0561) + + N + + Y + + X + + + + + + + + + +
+ Order Callback Phone Number + + (0040,2010) + + N + + N + + X + + + + + + + + + +
+ Order Callback Telecom Information + + (0040,2011) + + N + + N + + X + + + + + + + + + +
+ Order Entered By + + (0040,2008) + + N + + N + + X + + + + + + + + + +
+ Order Enterer Location + + (0040,2009) + + N + + N + + X + + + + + + + + + +
+ Other Patient IDs + + (0010,1000) + + N + + Y + + X + + + + + + + + + +
+ Other Patient IDs Sequence + + (0010,1002) + + N + + Y + + X + + + + + + + + + +
+ Other Patient Names + + (0010,1001) + + N + + Y + + X + + + + + + + + + +
+ Overlay Comments + + (60xx,4000) + + Y + + N + + X + + + + + + + + + + C +
+ Overlay Data + + (60xx,3000) + + N + + Y + + X + + + + + + + + + + C +
+ Overlay Date + + (0008,0024) + + Y + + Y + + X + + + + + + K + + C + + + +
+ Overlay Time + + (0008,0034) + + Y + + Y + + X + + + + + + K + + C + + + +
+ Palette Color Lookup Table UID + + (0028,1199) + + N + + Y + + U + + + K + + + + + + + +
+ Participant Sequence + + (0040,A07A) + + N + + Y + + X + + + + + + + + + +
+ Patient Address + + (0010,1040) + + N + + N + + X + + + + + + + + + +
+ Patient Comments + + (0010,4000) + + N + + Y + + X + + + + + + + + C + + +
+ Patient ID + + (0010,0020) + + N + + Y + + Z + + + + + + + + + +
+ Patient Sex Neutered + + (0010,2203) + + N + + Y + + X/Z + + + + + K + + + + + +
+ Patient State + + (0038,0500) + + N + + N + + X + + + + + C + + + + C + + +
+ Patient Transport Arrangements + + (0040,1004) + + N + + N + + X + + + + + + + + + +
+ Patient's Age + + (0010,1010) + + N + + Y + + X + + + + + K + + + + + +
+ Patient's Birth Date + + (0010,0030) + + N + + Y + + Z + + + + + + + + + +
+ Patient's Birth Name + + (0010,1005) + + N + + N + + X + + + + + + + + + +
+ Patient's Birth Time + + (0010,0032) + + N + + Y + + X + + + + + + + + + +
+ Patient's Institution Residence + + (0038,0400) + + N + + N + + X + + + + + + + + + +
+ Patient's Insurance Plan Code Sequence + + (0010,0050) + + + + X + + + + + + + + + +
+ Patient's Mother's Birth Name + + (0010,1060) + + N + + N + + X + + + + + + + + + +
+ Patient's Name + + (0010,0010) + + N + + Y + + Z + + + + + + + + + +
+ Patient's Primary Language Code Sequence + + (0010,0101) + + + + X + + + + + + + + + +
+ Patient's Primary Language Modifier Code Sequence + + (0010,0102) + + + + X + + + + + + + + + +
+ Patient's Religious Preference + + (0010,21F0) + + N + + N + + X + + + + + + + + + +
+ Patient's Sex + + (0010,0040) + + N + + Y + + Z + + + + + K + + + + + +
+ Patient's Size + + (0010,1020) + + N + + Y + + X + + + + + K + + + + + +
+ Patient's Telecom Information + + (0010,2155) + + N + + N + + X + + + + + + + + + +
+ Patient's Telephone Numbers + + (0010,2154) + + N + + N + + X + + + + + + + + + +
+ Patient's Weight + + (0010,1030) + + N + + Y + + X + + + + + K + + + + + +
+ Performed Location + + (0040,0243) + + N + + N + + X + + + + + + + + + +
+ Performed Procedure Step Description + + (0040,0254) + + N + + Y + + X + + + + + + + + C + + +
+ Performed Procedure Step End Date + + (0040,0250) + + N + + Y + + X + + + + + + K + + C + + + +
+ Performed Procedure Step End DateTime + + (0040,4051) + + N + + N + + X + + + + + + K + + C + + + +
+ Performed Procedure Step End Time + + (0040,0251) + + N + + Y + + X + + + + + + K + + C + + + +
+ Performed Procedure Step ID + + (0040,0253) + + N + + Y + + X + + + + + + + + + +
+ Performed Procedure Step Start Date + + (0040,0244) + + N + + Y + + X + + + + + + K + + C + + + +
+ Performed Procedure Step Start DateTime + + (0040,4050) + + N + + N + + X + + + + + + K + + C + + + +
+ Performed Procedure Step Start Time + + (0040,0245) + + N + + Y + + X + + + + + + K + + C + + + +
+ Performed Station AE Title + + (0040,0241) + + N + + N + + X + + + + K + + + + + + +
+ Performed Station Geographic Location Code Sequence + + (0040,4030) + + N + + N + + X + + + + K + + + + + + +
+ Performed Station Name + + (0040,0242) + + N + + N + + X + + + + K + + + + + + +
+ Performed Station Name Code Sequence + + (0040, 4028) + + N + + N + + X + + + + K + + + + + + +
+ Performing Physician Identification Sequence + + (0008,1052) + + N + + Y + + X + + + + + + + + + +
+ Performing Physicians' Name + + (0008,1050) + + N + + Y + + X + + + + + + + + + +
+ Person Address + + (0040,1102) + + N + + Y + + X + + + + + + + + + +
+ Person Identification Code Sequence + + (0040,1101) + + N + + Y + + D + + + + + + + + + +
+ Person Name + + (0040,A123) + + N + + Y + + D + + + + + + + + + +
+ Person's Telecom Information + + (0040,1104) + + N + + Y + + X + + + + + + + + + +
+ Person's Telephone Numbers + + (0040,1103) + + N + + Y + + X + + + + + + + + + +
+ Physician Approving Interpretation + + (4008,0114) + + Y + + N + + X + + + + + + + + + +
+ Physician(s) Reading Study Identification Sequence + + (0008,1062) + + N + + Y + + X + + + + + + + + + +
+ Physician(s) of Record + + (0008,1048) + + N + + Y + + X + + + + + + + + + +
+ Physician(s) of Record Identification Sequence + + (0008,1049) + + N + + Y + + X + + + + + + + + + +
+ Placer Order Number / Imaging Service Request + + (0040,2016) + + N + + Y + + Z + + + + + + + + + +
+ Plate ID + + (0018,1004) + + N + + Y + + X + + + + K + + + + + + +
+ Pre-Medication + + (0040,0012) + + N + + N + + X + + + + + C + + + + + +
+ Pregnancy Status + + (0010,21C0) + + N + + N + + X + + + + + K + + + + + +
+ Presentation Display Collection UID + + (0070,1101) + + N + + Y + + U + + + K + + + + + + + +
+ Presentation Sequence Collection UID + + (0070,1102) + + N + + Y + + U + + + K + + + + + + + +
+ Procedure Step Cancellation DateTime + + (0040,4052) + + N + + N + + X + + + + + + K + + C + + + +
+ + Private attributes + + + + (gggg,eeee) where gggg is odd + + + N + + N + + X + + C + + + + + + + + +
+ Protocol Name + + (0018,1030) + + N + + Y + + X/D + + + + + + + + C + + +
+ Reason for Omission Description + + (300C,0113) + + N + + Y + + X + + + + + + + + C + + +
+ Reason for the Imaging Service Request + + (0040,2001) + + Y + + N + + X + + + + + + + + C + + +
+ Reason for Study + + (0032,1030) + + Y + + N + + X + + + + + + + + C + + +
+ Referenced Digital Signature Sequence + + (0400,0402) + + N + + Y + + X + + + + + + + + + +
+ Referenced Frame of Reference UID + + (3006,0024) + + N + + Y + + U + + + K + + + + + + + +
+ Referenced General Purpose Scheduled Procedure Step Transaction UID + + (0040,4023) + + Y + + N + + U + + + K + + + + + + + +
+ Referenced Image Sequence + + (0008,1140) + + N + + Y + + X/Z/U* + + + K + + + + + + + +
+ Referenced Observation UID (Trial) + + (0040,A172) + + Y + + N + + U + + + K + + + + + + + +
+ Referenced Patient Alias Sequence + + (0038, 0004) + + N + + N + + X + + + + + + + + + +
+ Referenced Patient Photo Sequence + + (0010,1100) + + N + + Y + + X + + + + + + + + + +
+ Referenced Patient Sequence + + (0008,1120) + + N + + Y + + X + + + X + + + + + + + +
+ Referenced Performed Procedure Step Sequence + + (0008,1111) + + N + + Y + + X/Z/D + + + K + + + + + + + +
+ Referenced SOP Instance MAC Sequence + + (0400,0403) + + N + + Y + + X + + + + + + + + + +
+ Referenced SOP Instance UID + + (0008,1155) + + N + + Y + + U + + + K + + + + + + + +
+ Referenced SOP Instance UID in File + + (0004,1511) + + N + + N + + U + + + K + + + + + + + +
+ Referenced Study Sequence + + (0008,1110) + + N + + Y + + X/Z + + + K + + + + + + + +
+ Referring Physician's Address + + (0008,0092) + + N + + N + + X + + + + + + + + + +
+ Referring Physician Identification Sequence + + (0008,0096) + + N + + Y + + X + + + + + + + + + +
+ Referring Physician's Name + + (0008,0090) + + N + + Y + + Z + + + + + + + + + +
+ Referring Physician's Telephone Numbers + + (0008,0094) + + N + + N + + X + + + + + + + + + +
+ Region of Residence + + (0010,2152) + + N + + N + + X + + + + + + + + + +
+ Related Frame of Reference UID + + (3006,00C2) + + N + + Y + + U + + + K + + + + + + + +
+ Request Attributes Sequence + + (0040,0275) + + N + + Y + + X + + + + + + + + C + + +
+ Requested Contrast Agent + + (0032,1070) + + N + + N + + X + + + + + + + + C + + +
+ Requested Procedure Comments + + (0040,1400) + + N + + N + + X + + + + + + + + C + + +
+ Requested Procedure Description + + (0032,1060) + + N + + Y + + X/Z + + + + + + + + C + + +
+ Requested Procedure ID + + (0040,1001) + + N + + N + + X + + + + + + + + + +
+ Requested Procedure Location + + (0040,1005) + + N + + N + + X + + + + + + + + + +
+ Requested SOP Instance UID + + (0000,1001) + + N + + N + + U + + + K + + + + + + + +
+ Requesting Physician + + (0032,1032) + + N + + N + + X + + + + + + + + + +
+ Requesting Service + + (0032,1033) + + N + + N + + X + + + + + + + + + +
+ Responsible Organization + + (0010,2299) + + N + + Y + + X + + + + + + + + + +
+ Responsible Person + + (0010,2297) + + N + + Y + + X + + + + + + + + + +
+ Results Comments + + (4008,4000) + + Y + + N + + X + + + + + + + + C + + +
+ Results Distribution List Sequence + + (4008,0118) + + Y + + N + + X + + + + + + + + + +
+ Results ID Issuer + + (4008,0042) + + Y + + N + + X + + + + + + + + + +
+ Reviewer Name + + (300E,0008) + + N + + Y + + X/Z + + + + + + + + + +
+ Scheduled Human Performers Sequence + + (0040,4034) + + N + + N + + X + + + + + + + + + +
+ Scheduled Patient Institution Residence + + (0038,001E) + + Y + + N + + X + + + + + + + + + +
+ Scheduled Performing Physician Identification Sequence + + (0040,000B) + + N + + N + + X + + + + + + + + + +
+ Scheduled Performing Physician Name + + (0040,0006) + + N + + N + + X + + + + + + + + + +
+ Scheduled Procedure Step End Date + + (0040,0004) + + N + + N + + X + + + + + + K + + C + + + +
+ Scheduled Procedure Step End Time + + (0040,0005) + + N + + N + + X + + + + + + K + + C + + + +
+ Scheduled Procedure Step Description + + (0040,0007) + + N + + Y + + X + + + + + + + + C + + +
+ Scheduled Procedure Step Location + + (0040,0011) + + N + + N + + X + + + + K + + + + + + +
+ Scheduled Procedure Step Modification DateTime + + (0040,4010) + + N + + N + + X + + + + + + K + + C + + + +
+ Scheduled Procedure Step Start Date + + (0040,0002) + + N + + N + + X + + + + + + K + + C + + + +
+ Scheduled Procedure Step Start DateTime + + (0040,4005) + + N + + N + + X + + + + + + K + + C + + + +
+ Scheduled Procedure Step Start Time + + (0040,0003) + + N + + N + + X + + + + + + K + + C + + + +
+ Scheduled Station AE Title + + (0040,0001) + + N + + N + + X + + + + K + + + + + + +
+ Scheduled Station Geographic Location Code Sequence + + (0040,4027) + + N + + N + + X + + + + K + + + + + + +
+ Scheduled Station Name + + (0040,0010) + + N + + N + + X + + + + K + + + + + + +
+ Scheduled Station Name Code Sequence + + (0040,4025) + + N + + N + + X + + + + K + + + + + + +
+ Scheduled Study Location + + (0032,1020) + + Y + + N + + X + + + + K + + + + + + +
+ Scheduled Study Location AE Title + + (0032,1021) + + Y + + N + + X + + + + K + + + + + + +
+ Series Date + + (0008,0021) + + N + + Y + + X/D + + + + + + K + + C + + + +
+ Series Description + + (0008,103E) + + N + + Y + + X + + + + + + + + C + + +
+ Series Instance UID + + (0020,000E) + + N + + Y + + U + + + K + + + + + + + +
+ Series Time + + (0008,0031) + + N + + Y + + X/D + + + + + + K + + C + + + +
+ Service Episode Description + + (0038,0062) + + N + + Y + + X + + + + + + + + C + + +
+ Service Episode ID + + (0038,0060) + + N + + Y + + X + + + + + + + + + +
+ Smoking Status + + (0010,21A0) + + N + + N + + X + + + + + K + + + + + +
+ SOP Instance UID + + (0008,0018) + + N + + Y + + U + + + K + + + + + + + +
+ Source Image Sequence + + (0008,2112) + + N + + Y + + X/Z/U* + + + K + + + + + + + +
+ Source Serial Number + + (3008,0105) + + N + + Y + + X + + + + K + + + + + + +
+ Special Needs + + (0038,0050) + + N + + N + + X + + + + + C + + + + + +
+ Start Acquisition DateTime + + (0018,9516) + + N + + Y + + X/D + + + + + + K + + C + + + +
+ Station Name + + (0008,1010) + + N + + Y + + X/Z/D + + + + K + + + + + + +
+ Storage Media File-set UID + + (0088,0140) + + N + + Y + + U + + + K + + + + + + + +
+ Study Comments + + (0032,4000) + + Y + + N + + X + + + + + + + + C + + +
+ Study Date + + (0008,0020) + + N + + Y + + Z + + + + + + K + + C + + + +
+ Study Description + + (0008,1030) + + N + + Y + + X + + + + + + + + C + + +
+ Study ID + + (0020,0010) + + N + + Y + + Z + + + + + + + + + +
+ Study ID Issuer + + (0032,0012) + + Y + + N + + X + + + + + + + + + +
+ Study Instance UID + + (0020,000D) + + N + + Y + + U + + + K + + + + + + + +
+ Study Time + + (0008,0030) + + N + + Y + + Z + + + + + + K + + C + + + +
+ Synchronization Frame of Reference UID + + (0020,0200) + + N + + Y + + U + + + K + + + + + + + +
+ Target UID + + (0018,2042) + + N + + Y + + U + + + K + + + + + + + +
+ Telephone Number (Trial) + + (0040,A354) + + Y + + N + + X + + + + + + + + + +
+ Template Extension Creator UID + + (0040,DB0D) + + Y + + N + + U + + + K + + + + + + + +
+ Template Extension Organization UID + + (0040,DB0C) + + Y + + N + + U + + + K + + + + + + + +
+ Text Comments + + (4000,4000) + + Y + + N + + X + + + + + + + + + +
+ Text String + + (2030,0020) + + N + + N + + X + + + + + + + + + +
+ Timezone Offset From UTC + + (0008,0201) + + N + + Y + + X + + + + + + K + + C + + + +
+ Topic Author + + (0088,0910) + + Y + + N + + X + + + + + + + + + +
+ Topic Keywords + + (0088,0912) + + Y + + N + + X + + + + + + + + + +
+ Topic Subject + + (0088,0906) + + Y + + N + + X + + + + + + + + + +
+ Topic Title + + (0088,0904) + + Y + + N + + X + + + + + + + + + +
+ Tracking UID + + (0062,0021) + + N + + Y + + U + + + K + + + + + + + +
+ Transaction UID + + (0008,1195) + + N + + N + + U + + + K + + + + + + + +
+ UID + + (0040,A124) + + N + + Y + + U + + + + + + + + + +
+ Verbal Source (Trial) + + (0040,A352) + + Y + + N + + X + + + + + + + + + +
+ Verbal Source Identifier Code Sequence (Trial) + + (0040,A358) + + Y + + N + + X + + + + + + + + + +
+ Verifying Observer Identification Code Sequence + + (0040,A088) + + N + + Y + + Z + + + + + + + + + +
+ Verifying Observer Name + + (0040,A075) + + N + + Y + + D + + + + + + + + + +
+ Verifying Observer Sequence + + (0040,A073) + + N + + Y + + D + + + + + + + + + +
+ Verifying Organization + + (0040,A027) + + N + + Y + + X + + + + + + + + + +
+ Visit Comments + + (0038,4000) + + N + + N + + X + + + + + + + + C + + +
+
+
+ Re-identifier + + An Application may claim conformance to an Application Level Confidentiality Profile as a re-identifier if it is capable of removing the protection from a protected SOP instance given that the recipient keys required for the decryption of one or more of the Encrypted Content (0400,0520) Attributes within the Encrypted Attributes Sequence (0400,0500) of the SOP instance are available. Removal of protection in this context is defined as the following process: + + + The application shall decrypt, using its recipient key, one instance of the Encrypted Content (0400,0520) Attribute within the Encrypted Attributes Sequence (0400,0500) and decode the resulting block of bytes into a DICOM dataset using the Transfer Syntax specified in the Encrypted Content Transfer Syntax UID (0400,0510). Re-identifiers claiming conformance to this profile shall be capable of decrypting the Encrypted Content using either AES or Triple-DES in all possible key lengths specified in this profile. + +If the application is able to decode more than one instance of the Encrypted Content (0400,0520) Attribute within the Encrypted Attributes Sequence (0400,0500), it is at the discretion of the application to choose any one of them. + + + + The application shall move all Attributes contained in the single item of the Modified Attributes Sequence (0400,0550) of the decoded dataset into the main dataset, replacing "dummy value" Attributes that may be present in the main dataset. + + + + Re-identification does not imply a complete reconstruction of the original SOP Instance, since it is not required that all Attributes being protected be part of the Encrypted Attributes Data Set. If the original UIDs are part of the Encrypted Attributes Data Set, they might be usable to gain access to the original, unprotected SOP Instance. + + + The presence of an encrypted data set that cannot be decrypted indicates that some or all of the attribute values in the message may not be real (they are dummies). Therefore, the recipient must not assume that any value in the message is diagnostically relevant. + + + + + + The attribute Patient Identity Removed (0012,0062) shall be replaced or added to the dataset with a value of NO and De-identification Method (0012,0063) and De-identification Method Code Sequence (0012,0064) shall be removed. + + +
+
+ Conformance Requirements + + The Conformance Statement of an application that claims conformance to an Application Level Confidentiality Profile shall describe: + + +which Attributes are removed during protection; + + +which Attributes are replaced by dummy values and how the dummy values are generated; + + +which Attributes are included in Encrypted Attributes Data Sets for later re-identification, and any pertinent details about how keys are selected for performing the encryption; + + +the scope across which the application is able to ensure referential integrity of replacement values for references such as SOP Instance UID, Frame of Reference UID, etc. if multiple SOP instances are protected (e.g., across multiple Studies, consistent replacement if the same Study processed more than once, etc.); + + +which Attributes and Attribute values are inserted during protection of a SOP instance; + + +which Transfer Syntaxes are supported for encoding/decoding of the Encrypted Attributes Data Set; + + +which Options are supported; + + +any additional restrictions (e. g. key sizes for public keys). + + +
+
+
+ Basic Application Level Confidentiality Profile + + This profile is intended for use in clinical trials, and other scenarios in which de-identification may be required, such as creation of teaching files, other types of publication, as well as submission of images and associated information to registries, such as oncology or radiation dose registries. + This Basic Application Level Confidentiality Profile defines an extremely conservative approach that removes all information related to: + + +the identity and demographic characteristics of the patient + + +the identity of any responsible parties or family members + + +the identity of any personnel involved in the procedure + + +the identity of the organizations involved in ordering or performing the procedure + + +additional information that could be used to match instances if given access to the originals, such as UIDs, dates and times + + +private attributes + + +when that information is present in the non-Pixel Data Attributes, including graphics or overlays, as described in . + + Unless the Clean Pixel Data Option is also specified, this profile does not address information burned-in to the pixels. + + The Attribute Longitudinal Temporal Information Modified (0028,0303) shall be added to the Dataset with a value of "REMOVED" if none of the Retain Longitudinal Temporal Information Options is applied. +
+
+ Basic Application Level Confidentiality Options + + Various options are defined to be applicable to the Basic Application Level Confidentiality Profile. Some of these options require removal of additional information, and some of these options require retention of information that would otherwise be removed. + The following options are defined that require removal of additional information: + + +Clean Pixel Data Option + + +Clean Recognizable Visual Features Option + + +Clean Graphics Option + + +Clean Structured Content Option + + +Clean Descriptors Option + + +The following options are defined that require retention of information that would otherwise be removed but that is needed for specific uses: + + +Retain Longitudinal Temporal Information with Full Dates Option + + +Retain Longitudinal Temporal Information with Modified Dates Option + + +Retain Patient Characteristics Option + + +Retain Device Identity Option + + +Retain UIDs + + +Retain Safe Private Option + + +
+ Clean Pixel Data Option + + When this Option is specified in addition to an Application Level Confidentiality Profile, any information burned in to the Pixel Data (7FE0,0010) corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed, as described in . + This may require intervention of or approval by a human operator. + The Attribute Burned In Annotation (0028,0301) shall be added to the Dataset with a value of "NO". + + + + This capability is called out as a specific option, since it may be extremely burdensome in practice to implement and is unnecessary for the vast majority of modalities that do not burn in such annotation in the first place. For example, CT images do not normally contain such burned in annotation, whereas Ultrasound images routinely do. + + + Though image processing and optical character recognition techniques can be used to detect the presence of and location of burned in text, and matching against known identifying information can be applied, deciding whether or not that text is identifying information or some other type of information may be non-trivial. Compliance with this option requires that identifying information is removed, regardless of how that is achieved. It is not required that information specified to be retained in the non-pixel data by other Options (e.g., physical characteristics, dates or descriptors) also be retained burned-in to the pixel data. Thus the most conservative approach of removing any and all burned in text would be compliant. This may involve sacrificing additional potentially useful information such as localizer posting and manual graphic annotations. + + + The stored pixel values are to be changed (blacked out); it is not sufficient to superimpose an overlay or graphic annotation or shutter to obscure the pixel data values, since those may not be ignored by the receiving system. + + + This option is intended to apply to the Pixel Data (7FE0,0010) Attribute that occurs in the top level Dataset of an Image Storage SOP Instance. The other standard use of Pixel Data (7FE0,0010) is within Icon Image Sequence (0088,0200), which is already described in and the accompanying note as requiring removal. This option does not require the ability to manually or automatically process the pixel values of Pixel Data (7FE0,0010) occurring in any other location than the top level dataset, but it does not prohibit it. Pixel Data (7FE0,0010) occurring within private Attributes will be removed because such Attributes will not be known to be safe. + + + +
+
+ Clean Recognizable Visual Features Option + + When this Option is specified in addition to an Application Level Confidentiality Profile, if there is sufficient visual information within the Pixel Data of a set of instances to allow an individual to be recognized from the instances themselves or a reconstruction of a set of instances, then sufficient removal or distortion of the Pixel Data shall be applied to prevent recognition. + This may require intervention of or approval by a human operator. + The Attribute Recognizable Visual Features (0028,0302) shall be added to the Dataset with a value of "NO". + + + + This capability is called out as a specific option, since it may be extremely burdensome in practice to implement and is unnecessary for the vast majority of anatomic sites and modalities. + + + In the case of full-face photographs, the risk of visual identification is obvious, and numerous techniques are well established for de-identification, such as applying black rectangles over the eyes, etc. + + + In the case of high-resolution cross-sectional imaging of the entire head and neck, it has been suggested that a 3D volume or surface rendering of the pixel data may be sufficient to allow identification (or matching against a constrained subset of individuals) under some circumstances. + + + Application of this option may render the pixel data unusable for the purpose for which it has been collected, and hence its use may require a compromise between de-identification and utility based on obtaining appropriate ethical approval and informed consent. Consider for example, the case of dental images. + + + Since the Referenced Patient Photo Sequence is removed as part of the Basic Profile, support of the Clean Recognizable Visual Features option does not add requirements for that attribute. + + + +
+
+ Clean Graphics Option + + Instances of various Standard and Standard Extended SOP Classes, including Images, Presentation States and other Composite SOP Instances, may contain identification information encoded as graphics, text annotations or overlays. This does not include information contained in Structured Report SOP Classes. + When this Option is specified in addition to an Application Level Confidentiality Profile, any information encoded in graphics, text annotations or overlays corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed, as described in . + This may require intervention of a human operator. + + + + This capability is called out as a specific option, since it may be more practical to simply remove all such graphics, text annotations or overlays (as required by the profile without this option). + + + As with burned-in pixel data annotation, deciding whether or not text is identifying information or some other type of information may be non-trivial. It is not required that information specified to be retained in the non-pixel data by other Options (e.g., physical characteristics, dates or descriptors) also be retained in graphics, text annotations or overlays. + + + +
+
+ Clean Structured Content Option + + Instances of Structured Report SOP Classes may contain identifiable information in a Content Sequence (0040,A730) encoded in Content Items. Instances of other SOP Classes may contain structured content encoded in a similar manner in the Acquisition Context Sequence (0040,0555) or Specimen Preparation Sequence (0040,0610). + When this Option is specified in addition to an Application Level Confidentiality Profile, any information encoded in SR Content Items or Acquisition Context or Specimen Preparation Sequence Items corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed. + + + + For example, the "observer" responsible for a diagnostic imaging report may be explicitly identified in Observation Content related Content Items in an SR. + + + A de-identifier that does not implement this option creates significant risk when attempting to de-identity a Structured Report unless it is only used to de-identify instances that are known to have no identifying information in the Content Sequence. + + + +
+
+ Clean Descriptors Option + + Even though many Attributes are defined in the DICOM Standard for specific purposes, such as to describe a Study or a Series, those that contain plain text over which an operator has control may contain unstructured information that includes identities. + When this Option is specified in addition to an Application Level Confidentiality Profile, any information that is embedded in text or string Attributes corresponding to the Attribute information specified to be removed by the Profile and any other Options specified shall also be removed, as described in . + + + + For example, an operator may include a person's name or a patient's demographics or physical characteristics in the Study Description (0008,1030), perhaps because their modality user interface does not provide other fields or because other systems do not display them. E.g., the description might contain "CT chest abdomen pelvis - 55F Dr. Smith". + + + One approach to cleaning such text strings without human intervention is to extract and retain only values known to be useful and safe and discard all others. For example, in the string "CT chest abdomen pelvis - 55F Dr. Smith" are found in Study Description (0008,1030), then it would be feasible to detect and retain "CT chest abdomen pelvis" and discard the remainder. In an international setting, this may require an extensive dictionary of words that are safe to retain, e.g., to detect "Buik" for abdomen in Dutch or "λεκάνη" for pelvis in Greek. Another possibility is to extract such information and attempt to code the information in other Attributes (if otherwise absent or empty) such as Anatomic Region Sequence (0008,2218). However, the possibility of string values being both identifying and descriptive in different uses needs to be considered, e.g., "Dr. Hand" or "M. Genou". + + + + calls out specific Attributes known to be at risk, but an implementer may want to consider any attribute that could potential contain character data, though this Option does not require that this be done. For example, all SH, LO, ST, LT and UT Value Representations could perhaps be misused. Code strings, CS, are not generally at risk, but a check against known Defined Terms and Enumerated Values could be performed. Though extremely unusual, it is conceivable that even a DS or IS string could be misused, and a check could be made that only legal numeric characters were used. Any PN Attribute is obviously at risk. The OB VR is discussed in the Retain Safe Private Option. + + + This Option specifies what needs to be removed, not what needs to be retained. Depending on the application, it may be desirable to retain some information, such as technique description, but discard other information, such as diagnosis, for example because it may bias the interpretation in a clinical trial. For example, one approach is to remove all description and comment attributes except Series Description (0008,103E), since this Attribute rarely contains identifying or diagnosis information yet is typically a reliable source of useful information about the acquisition technique populated automatically from modality device protocols, though it still could be cleaned as described in Note 2. + + + It should be recognized that if any descriptor contains information about a particularly unusual procedure or condition, then in conjunction with other demographic information it might reduce the number of possible individuals that could be the imaging subject. However, this is to some extent true also if the condition or other unusual physical features are obvious from visual examination of the images themselves. E.g., how many conjoined twins born in a particular month in Philadelphia might there be? + + + + The manner of cleaning shall be described in the Conformance Statement. +
+
+ Retain Longitudinal Temporal Information Options + + Dates and times are recognized as having a potential for leakage of identity because they constrain the number of possible individuals that could be the imaging subject, though only if there is access to other information about the individuals concerned to match it against. + However, there are applications that require dates and times to be present to able to fulfill the objective. This is particularly true in therapeutic clinical trials in which the objective is to measure change in an outcome measure over time. Further, it is often necessary to correlate information from images with information from other sources, such as clinical and laboratory data, and dates and times need to be consistent. + Two options are specified to address these requirements: + + +Retain Longitudinal Temporal Information With Full Dates Option + + +Retain Longitudinal Temporal Information With Modified Dates Option + + +When the Retain Longitudinal Temporal Information With Full Dates Option is specified in addition to an Application Level Confidentiality Profile, any dates and times present in the Attributes shall be retained, as described in . The Attribute Longitudinal Temporal Information Modified (0028,0303) shall be added to the Dataset with a value of "UNMODIFIED". + When the Retain Longitudinal Temporal Information With Modified Dates Option is specified in addition to an Application Level Confidentiality Profile, any dates and times present in the Attributes listed in shall be modified. The modification of the dates and times shall be performed in a manner that: + + +aggregates or transforms dates so as to reduce the possibility of matching for re-identification + + +preserves the gross longitudinal temporal relationships between images obtained on different dates to the extent necessary for the application + + +preserves the fine temporal relationships between images and real-world events to the extent necessary for analysis of the images for the application + + +The Attribute Longitudinal Temporal Information Modified (0028,0303) shall be added to the Dataset with a value of "MODIFIED". + + + + Aggregation of dates may be performed by various means such as setting all dates to the first day of the month, all months to the first month of the year, etc., depending on the precision required for the application. + + + It is possible to modify all dates and times to dummy values by shifting them relative to an arbitrary epoch, and hence retain the precise longitudinal temporal relationships amongst a set of studies, when either de-identification of the entire set is performed at the same time, or some sort of mapping or database is kept to repeat this process on separate occasions. + + + Transformation of dates and times should be considered together, in order to address studies that span midnight. + + + Any transformation of times should be performed in such a manner as to not disrupt computations needed for analysis, such as comparison of start of injection time to the acquisition time for PET SUV, or extraction of time-intensity values from dynamic contrast enhanced studies. + + + + The manner of date modification shall be described in the Conformance Statement. +
+
+ Retain Patient Characteristics Option + + Physical characteristics of the patient, which are descriptive rather than identifying information per se, are recognized as having a potential for leakage of identity because they constrain the number of possible individuals that could be the imaging subject, though only if there is access to other information about the individuals concerned to match it against. + However, there are applications that require such physical characteristics in order to perform the computations necessary to analyze the images to fulfill the objective. One such class of applications is those that are related to metabolic measures, such as computation of PET Standard Uptake Values (SUV) or DEXA or MRI measures of body composition, which are based on body weight, body surface area or lean body mass. + When this Option is specified in addition to an Application Level Confidentiality Profile, information about age, sex, height and weight and other characteristics present in the Attributes shall be retained, as described in . + The manner of cleaning of retained attributes shall be described in the Conformance Statement. +
+
+ Retain Device Identity Option + + Information about the identity of the device that was used to perform the acquisition is recognized as having a potential for leakage of identity because it may constrain the number of possible individuals that could be the imaging subject, though only if there is access to other information about the individuals concerned to match it against. + However, there are applications that require such device information to perform the analysis or interpretation. The type of correction for spatial or other inhomogeneity may require knowledge of the specific device serial number. Confirmation that specific devices that have been previously qualified (e.g., with phantoms) may be required. Further, there may be a need to maintain a record of the device used for regulatory or registry purposes, yet the acquisition site may not maintain an adequate electronic audit trail. + When this Option is specified in addition to an Application Level Confidentiality Profile, information about the identity of the device in the Attributes shall be retained, as described in . +
+
+ Retain UIDs Option + + Though individuals do not have unique identifiers themselves, studies, series, instances and other entities in the DICOM model are assigned globally unique UIDs. Whilst these UIDs cannot be mapped directly to an individual out of context, given access to the original images, or to a database of the original images containing the UIDs, it would be possible to recover the individual's identity. + However, there are applications that require the ability to maintain an audit trail back to the original images and though there are other mechanisms they may not scale well or be reliably implemented. This Option is provided for use when it is judged that the risk of gaining access to the original information via the UIDs is small relative to the benefit of retaining them. + When this Option is specified in addition to an Application Level Confidentiality Profile, UIDs shall be retained, as described in . + + + + A UID of a DICOM entity is not the same as a unique identifier of an individual, such as would be proscribed by some privacy regulations. + + + UIDs are generated using a hierarchical scheme of "roots", which may be traceable by a knowledgeable person back to the original assignee of the root, typically the device manufacturer, but sometimes the organization using the device. + + + When evaluating the risk of matching UIDs with the original images or PACS database, one should consider that even if the UIDs are changed, the pixel data itself presents a similar risk. Specifically, the pixel data of the de-identified image can be matched against the pixel data of the original image. Such matching can be greatly accelerated by comparing pre-computed hash values of the pixel data. Removal of burned-in identification may change the pixel data but then matching against a sub-region of the pixel data is almost certainly possible (e.g., the central region of an image). Even addition of noise to an image is not sufficient to prevent re-identification since statistical matching techniques can be used. Ultimately, if any useable pixel data is retained during de-identification, then re-identification is nearly always possible if one has access to the original images. Ergo, replacement of UIDs should not give rise to a false confidence that the images have been more thoroughly de-identified than if the UIDs are retained. + + + Regardless of this option, implementers should take care not to remove UIDs that are structural and defined by the standard as opposed to those that are instance-related. E.g., one would never remove or replace the SOP Class UID for de-identification purposes. + + + The Implementation Class UID (0002,0012) is not included in the list of UID attributes to be retained, since it is part of the File Meta Information (see ), which is entirely replaced whenever a file is stored or modified during de-identification. See . + + + +
+
+ Retain Safe Private Option + + By definition, Private Attributes contain proprietary information, in many cases the nature of which is known only to the vendor and not publicly documented. + However, some Private Attributes may be necessary for the desired application. For example, specific technique information such as CT helical span pitch, or pixel value transformation, such as PET SUV rescale factors, may only be available in Private Attributes since the information is either not defined in Standard Attributes, or was added to the DICOM Standard after the acquisition device was manufactured. + When this Option is specified in addition to an Application Level Confidentiality Profile, Private Attributes that are known by the de-identifier to be safe from identity leakage shall be retained, together with the Private Creator IDs that are required to fully define the retained Private Attributes; all other Private Attributes shall be removed or processed in the element-specific manner recommended by Deidentification Action (0008,0307), if present within Private Data Element Characteristics Sequence (0008,0300) (see ). + Whether or not an Attribute is known to be safe may be determined by: + + + its presence in a block of Private Data Elements with a value of "SAFE" in Block Identifying Information Status (0008,0303) or individually listed in Nonidentifying Private Elements (gggg,0004) (within Private Data Element Characteristics Sequence (0008,0300); see ) + + + its presence in + + + + documentation in the Conformance Statement + + + some other means. + + + When this Option is not specified, all Private Attributes shall be removed, as described in . + + + + A sample list of Private Attributes thought to be safe is provided here. Vendors do not guarantee them to be safe, and do not commit to sending them in any particular software version (including future products). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Safe Private Attributes
+ + Data Element + + + + Private Creator + + + + VR + + + + VM + + + + Meaning + +
+ (7053,xx00) + + Philips PET Private Group + + DS + + 1 + + SUV Factor - Multiplying stored pixel values by Rescale Slope then this factor results in SUVbw in g/l +
+ (7053,xx09) + + Philips PET Private Group + + DS + + 1 + + Activity Concentration Factor - Multiplying stored pixel values by Rescale Slope then this factor results in MBq/ml. +
+ (00E1,xx21) + + ELSCINT1 + + DS + + 1 + + DLP +
+ (01E1,xx26) + + ELSCINT1 + + CS + + 1 + + Phantom Type +
+ (01E1,xx50) + + ELSCINT1 + + DS + + 1 + + Acquisition Duration +
+ (01F1,xx01) + + ELSCINT1 + + CS + + 1 + + Acquisition Type +
+ (01F1,xx07) + + ELSCINT1 + + DS + + 1 + + Table Velocity +
+ (01F1,xx26) + + ELSCINT1 + + DS + + 1 + + Pitch +
+ (01F1,xx27) + + ELSCINT1 + + DS + + 1 + + Rotation Time +
+ (0019,xx23) + + GEMS_ACQU_01 + + DS + + 1 + + Table Speed [mm/rotation] +
+ (0019,xx24) + + GEMS_ACQU_01 + + DS + + 1 + + Mid Scan Time [sec] +
+ (0019,xx27) + + GEMS_ACQU_01 + + DS + + 1 + + Rotation Speed (Gantry Period) +
+ (0019,xx9E) + + GEMS_ACQU_01 + + LO + + 1 + + Internal Pulse Sequence Name +
+ (0043,xx27) + + GEMS_PARM_01 + + SH + + 1 + + Scan Pitch Ratio in the form "n.nnn:1" +
+ (0045,xx01) + + GEMS_HELIOS_01 + + SS + + 1 + + Number of Macro Rows in Detector +
+ (0045,xx02) + + GEMS_HELIOS_01 + + FL + + 1 + + Macro width at ISO Center +
+ (0903,xx10) + + GEIIS PACS + + US + + 1 + + Reject Image Flag +
+ (0903,xx11) + + GEIIS PACS + + US + + 1 + + Significant Flag +
+ (0903,xx12) + + GEIIS PACS + + US + + 1 + + Confidential Flag +
+ (2001,xx03) + + Philips Imaging DD 001 + + FL + + 1 + + Diffusion B-Factor +
+ (2001,xx04) + + Philips Imaging DD 001 + + CS + + 1 + + Diffusion Direction +
+ (0019,xx0C) + + SIEMENS MR HEADER + + IS + + 1 + + B Value +
+ (0019,xx0D) + + SIEMENS MR HEADER + + CS + + 1 + + Diffusion Directionality +
+ (0019,xx0E) + + SIEMENS MR HEADER + + FD + + 3 + + Diffusion Gradient Direction +
+ (0019,xx27) + + SIEMENS MR HEADER + + FD + + 6 + + B Matrix +
+ (0043,xx39) + + GEMS_PARM_01 + + IS + + 4 + + 1stvalue is B Value +
+ (0043,xx6F) + + GEMS_PARM_01 + + DS + + 3-4 + + Scanner Table Entry + Gradient Coil Selected +
+ (0025,xx07) + + GEMS_SERS_01 + + SL + + 1 + + Images in Series +
+ (7E01,xx01) + + HOLOGIC, Inc. + + LO + + 1 + + Codec Version +
+ (7E01,xx02) + + HOLOGIC, Inc. + + SH + + 1 + + Codec Content Type +
+ (7E01,xx10) + + HOLOGIC, Inc. + + SQ + + 1 + + High Resolution Data Sequence +
+ (7E01,xx11) + + HOLOGIC, Inc. + + SQ + + 1 + + Low Resolution Data Sequence +
+ (7E01,xx12) + + HOLOGIC, Inc. + + OB + + 1 + + Codec Content +
+ (0099,xx01) + + NQHeader + + UI + + 1 + + Version +
+ (0099,xx02) + + NQHeader + + UI + + 1 + + Analyzed Series UID +
+ (0099,xx04) + + NQHeader + + SS + + 1 + + Return Code +
+ (0099,xx05) + + NQHeader + + LT + + 1 + + Return Message +
+ (0099,xx10) + + NQHeader + + FL + + 1 + + MI +
+ (0099,xx20) + + NQHeader + + SH + + 1 + + Units +
+ (0099,xx21) + + NQHeader + + FL + + 1 + + ICV +
+ (0199,xx01) + + NQLeft + + FL + + 1 + + Left Cortical White Matter +
+ (0199,xx02) + + NQLeft + + FL + + 1 + + Left Cortical Gray Matter +
+ (0199,xx03) + + NQLeft + + FL + + 1 + + Left 3rd Ventricle +
+ (0199,xx04) + + NQLeft + + FL + + 1 + + Left 4th Ventricle +
+ (0199,xx05) + + NQLeft + + FL + + 1 + + Left 5th Ventricle +
+ (0199,xx06) + + NQLeft + + FL + + 1 + + Left Lateral Ventricle +
+ (0199,xx07) + + NQLeft + + FL + + 1 + + Left Inferior Lateral Ventricle +
+ (0199,xx08) + + NQLeft + + FL + + 1 + + Left Inferior CSF +
+ (0199,xx09) + + NQLeft + + FL + + 1 + + Left Cerebellar White Matter +
+ (0199,xx0a) + + NQLeft + + FL + + 1 + + Left Cerebellar Gray Matter +
+ (0199,xx0b) + + NQLeft + + FL + + 1 + + Left Hippocampus +
+ (0199,xx0c) + + NQLeft + + FL + + 1 + + Left Amygdala +
+ (0199,xx0d) + + NQLeft + + FL + + 1 + + Left Thalamus +
+ (0199,xx0e) + + NQLeft + + FL + + 1 + + Left Caudate +
+ (0199,xx0f) + + NQLeft + + FL + + 1 + + Left Putamen +
+ (0199,xx10) + + NQLeft + + FL + + 1 + + Left Pallidum +
+ (0199,xx11) + + NQLeft + + FL + + 1 + + Left Ventral Diencephalon +
+ (0199,xx12) + + NQLeft + + FL + + 1 + + Left Nucleus Accumbens +
+ (0199,xx13) + + NQLeft + + FL + + 1 + + Left Brain Stem +
+ (0199,xx14) + + NQLeft + + FL + + 1 + + Left Exterior CSF +
+ (0199,xx15) + + NQLeft + + FL + + 1 + + Left WM Hypo +
+ (0199,xx16) + + NQLeft + + FL + + 1 + + Left Other +
+ (0299,xx01) + + NQRight + + FL + + 1 + + Right Cortical White Matter +
+ (0299,xx02) + + NQRight + + FL + + 1 + + Right Cortical Gray Matter +
+ (0299,xx03) + + NQRight + + FL + + 1 + + Right 3rd Ventricle +
+ (0299,xx04) + + NQRight + + FL + + 1 + + Right 4th Ventricle +
+ (0299,xx05) + + NQRight + + FL + + 1 + + Right 5th Ventricle +
+ (0299,xx06) + + NQRight + + FL + + 1 + + Right Lateral Ventricle +
+ (0299,xx07) + + NQRight + + FL + + 1 + + Right Inferior Lateral Ventricle +
+ (0299,xx08) + + NQRight + + FL + + 1 + + Right Inferior CSF +
+ (0299,xx09) + + NQRight + + FL + + 1 + + Right Cerebellar White Matter +
+ (0299,xx0a) + + NQRight + + FL + + 1 + + Right Cerebellar Gray Matter +
+ (0299,xx0b) + + NQRight + + FL + + 1 + + Right Hippocampus +
+ (0299,xx0c) + + NQRight + + FL + + 1 + + Right Amygdala +
+ (0299,xx0d) + + NQRight + + FL + + 1 + + Right Thalamus +
+ (0299,xx0e) + + NQRight + + FL + + 1 + + Right Caudate +
+ (0299,xx0f) + + NQRight + + FL + + 1 + + Right Putamen +
+ (0299,xx10) + + NQRight + + FL + + 1 + + Right Pallidum +
+ (0299,xx11) + + NQRight + + FL + + 1 + + Right Ventral Diencephalon +
+ (0299,xx12) + + NQRight + + FL + + 1 + + Right Nucleus Accumbens +
+ (0299,xx13) + + NQRight + + FL + + 1 + + Right Brain Stem +
+ (0299,xx14) + + NQRight + + FL + + 1 + + Right Exterior CSF +
+ (0299,xx15) + + NQRight + + FL + + 1 + + Right WM Hypo +
+ (0299,xx16) + + NQRight + + FL + + 1 + + Right Other +
+ (2005,xx0D) + + Philips MR Imaging DD 001 + + FL + + 1 + + Scale Intercept +
+ (2005,xx0E) + + Philips MR Imaging DD 001 + + FL + + 1 + + Scale Slope +
+ (0119,xx00) + + SIEMENS Ultrasound SC2000 + + LO + + 1 + + Acoustic Meta Information Version +
+ (0119,xx01) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Common Acoustic Meta Information +
+ (0119,xx02) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + Multi Stream Sequence +
+ (0119,xx03) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + Acoustic Data Sequence +
+ (0119,xx04) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Per Transaction Acoustic Control Information +
+ (0119,xx05) + + SIEMENS Ultrasound SC2000 + + UL + + 1 + + Acoustic Data Offset +
+ (0119,xx06) + + SIEMENS Ultrasound SC2000 + + UL + + 1 + + Acoustic Data Length +
+ (0119,xx07) + + SIEMENS Ultrasound SC2000 + + UL + + 1 + + Footer Offset +
+ (0119,xx08) + + SIEMENS Ultrasound SC2000 + + UL + + 1 + + Footer Length +
+ (0119,xx09) + + SIEMENS Ultrasound SC2000 + + SS + + 1 + + Acoustic Stream Number +
+ (0119,xx10) + + SIEMENS Ultrasound SC2000 + + SH + + 1 + + Acoustic Stream Type +
+ (0119,xx11) + + SIEMENS Ultrasound SC2000 + + + + 1 + + Stage Timer Time +
+ (0119,xx12) + + SIEMENS Ultrasound SC2000 + + + + 1 + + Stop Watch Time +
+ (0119,xx13) + + SIEMENS Ultrasound SC2000 + + IS + + 1 + + Volume Rate +
+ (0119,xx21) + + SIEMENS Ultrasound SC2000 + + SH + + 1 + + +
+ (0129,xx00) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + MPR View Sequence +
+ (0129,xx02) + + SIEMENS Ultrasound SC2000 + + UI + + 1 + + Bookmark UID +
+ (0129,xx03) + + SIEMENS Ultrasound SC2000 + + + + 1 + + Plane Origin Vector +
+ (0129,xx04) + + SIEMENS Ultrasound SC2000 + + + + 1 + + Row Vector +
+ (0129,xx05) + + SIEMENS Ultrasound SC2000 + + + + 1 + + Column Vector +
+ (0129,xx06) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + Visualization Sequence +
+ (0129,xx07) + + SIEMENS Ultrasound SC2000 + + UI + + 1 + + Bookmark UID +
+ (0129,xx08) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Visualization Information +
+ (0129,xx09) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + Application State Sequence +
+ (0129,xx10) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Application State Information +
+ (0129,xx11) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + Referenced Bookmark Sequence +
+ (0129,xx12) + + SIEMENS Ultrasound SC2000 + + UI + + 1 + + Referenced Bookmark UID +
+ (0129,xx20) + + SIEMENS Ultrasound SC2000 + + SQ + + 1 + + Cine Parameters Sequence +
+ (0129,xx21) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Cine Parameters Schema +
+ (0129,xx22) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Values of Cine Parameters +
+ (0129,xx29) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + +
+ (0129,xx30) + + SIEMENS Ultrasound SC2000 + + CS + + 1 + + Raw Data Object Type +
+ (0139,xx01) + + SIEMENS Ultrasound SC2000 + + SL + + 1 + + Physio Capture ROI +
+ (0149,xx01) + + SIEMENS Ultrasound SC2000 + + FD + + 1-n + + Vector of BROI Points +
+ (0149,xx02) + + SIEMENS Ultrasound SC2000 + + FD + + 1-n + + Start/End Timestamps of Strip Stream +
+ (0149,xx03) + + SIEMENS Ultrasound SC2000 + + FD + + 1-n + + Timestamps of Visible R-waves +
+ (7FD1,xx01) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Acoustic Image and Footer Data +
+ (7FD1,xx09) + + SIEMENS Ultrasound SC2000 + + UI + + 1 + + Volume Version ID +
+ (7FD1,xx10) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + Volume Payload +
+ (7FD1,xx11) + + SIEMENS Ultrasound SC2000 + + OB + + 1 + + After Payload +
+ (7FD1,xx01) + + SIEMENS SYNGO ULTRA-SOUND TOYON DATA STREAMING + + OB + + 1 + + Padding +
+ (7FD1,xx09) + + SIEMENS SYNGO ULTRA-SOUND TOYON DATA STREAMING + + UI + + 1 + + Version ID +
+ (7FD1,xx10) + + SIEMENS SYNGO ULTRA-SOUND TOYON DATA STREAMING + + OB + + 1 + + Volume Payload +
+ (7FD1,xx11) + + SIEMENS SYNGO ULTRA-SOUND TOYON DATA STREAMING + + OB + + 1 + + After Payload +
+
+ + One approach to retaining Private Attributes safely, either when the VR is encoded explicitly or known from a data dictionary (such as may be derived from published DICOM Conformance Statements or previously encountered instances, perhaps by adaptively extending the data dictionary as new explicit VR instances are received), is to retain those Attributes that are numeric only. For example, one might retain US, SS, UL, SS, FL and FD binary values, and IS and DS string values that contain only valid numeric characters. One might assume that other string Value Representations are unsafe in the absence of definite confirmation from the vendor to the contrary; code strings (CS) may be an exception. Bulk binary data in OB Value representations is particularly unsafe, and may often contain entire proprietary format headers in binary or text or XML form that includes the patient's name and other identifying information. + +
+
+ The safe private attributes that are retained shall be described in the Conformance Statement. +
+
+
+ + Network Address Management Profiles + +
+ Basic Network Address Management Profile + + The Basic Network Address Management Profile utilizes DHCP to provide services to assign and manage IP parameters for machines remotely. The DHCP server is manually configured to establish the rules for assigning IP addresses to machines. The rules may be explicit machine by machine assignments and may be assignment of a block of IP addresses to be assigned dynamically as machines are attached and removed from the network. The DHCP client can obtain its IP address and a variety of related parameters such as NTP server address from the DHCP server during startup. The DHCP server may dynamically update the DNS server with new relationships between IP addresses and DNS hostnames. + The DNS Client can obtain the IP number for another host by giving the DNS hostname to a DNS Server and receive the IP number in response. This transaction may be used in other profiles or in implementations that do not conform to the Basic Network Address Management Profile. + The Basic Network Address Management Profile applies to the actors DHCP Server, DHCP Client, DNS Server, and DNS Client. The mandatory and optional transactions are described in the table and sections below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Basic Network Address Management Profile
+ Actor + + Transaction + + Optionality + + Section +
+ DHCP Server + + Configure DHCP Server + + M + + F.1.2 +
+ + Find and Use DHCP Server + + M + + F.1.3 +
+ + Maintain Lease + + M + + F.1.4 +
+ + Resolve Hostname + + M + + F.1.1 +
+ + DDNS Coordination + + O + + F.1.5 +
+ DHCP Client + + Find and Use DHCP Server + + M + + F.1.3 +
+ + Maintain Lease + + M + + F.1.4 +
+ DNS Server + + DDNS Coordination + + O + + F.1.5 +
+ + Resolve Hostname + + M + + F.1.1 +
+ DNS Client + + Resolve Hostname + + M + + F.1.1 +
+
+ Resolve Hostname + +
+ Scope + + The DNS Client can obtain the IP number for a host by giving the DNS hostname to a DNS Server and receive the IP number in response. +
+
+ Use Case Roles + + +
+ Resolve Hostname + + + + + + +
+
+ + +Actor: + +DNS Client + + + +Role: + +Needs IP address, has the DNS Hostname + + + +Actor: + +DNS Server + + + +Role: + +Provides current IP address when given the DNS Hostname + + + +
+
+ Referenced Standards + + The standards and their relationships for the family of DNS protocols are shown in . The details of transactions, transaction diagrams, etc. are contained within the referenced RFC's. + +
+ DNS Referenced Standards + + + + + + +
+
+
+
+ DNS Security Considerations (Informative) + + The issue of security is under active development by the Internet Engineering Task Force and its various working groups. The security related RFCs and drafts are identified in . Some of these are completed. Others are still in the draft stage. The Basic Network Address Management Profile does not include specific requirements for support of DNS security extensions by the DNS Client. + The Basic Network Address Management profile should not be used outside a secured environment. At a minimum there should be: + + +Firewall or router protections to ensure that only approved external hosts are used for DNS services. + + +Agreements for VPN and other access should require that DNS clients use only approved DNS servers over the VPN. + + + Other network security procedures such as automated intrusion detection may be appropriate in some environments. Security features beyond this minimum should be established by the local security policy and are beyond the scope of DICOM. + The purpose of the selected security is to limit the scope of the threat to insider attacks. The DNS system discloses only hostnames and IP addresses, so there is little concern about eavesdropping. The protections are to limit the exposure to denial of service attacks by counterfeit servers or clients. +
+
+ DNS Implementation Considerations (Informative) + + Client caches may cause confusion during updates. Many DNS clients check for DNS updates very infrequently and might not reflect DNS changes for hours or days. Manual steps may be needed to trigger immediate updates. Details for controls of cache and update vary for different DNS clients and DNS servers, but DNS caching and update propagation delays are significant factors and implementations have mechanisms to manage these issues. + DNS Server failure management should be considered. Redundant servers and fallback host files are examples of possible error management tools. +
+
+ Support For Service Discovery + + The DNS server may provide additional optional information in support of configuration management. See for the specification of this information and additional RFC's to be supported. +
+
+
+ Configure DHCPserver + +
+ Scope + + The DHCP server shall be configurable by site administration so that + + +DHCP clients can be added and removed. + + +DHCP clients configurations can be modified to set values for attributes used in later transactions. + + +pre-allocation of fixed IP addresses for DHCP clients is supported + + + This standard does not specify how this configuration is to be performed. + + Most DHCP servers support the pre-allocation of fixed IP addresses to simplify the transition process for legacy systems. This permits a particular device to switch to DHCP while retaining the previously assigned IP address. This enables the use of a central site management of IP addresses without breaking compatibility with older systems that require fixed IP addresses. + +
+
+ Use Case Roles + + +
+ Configure DHCP Server + + + + + + +
+
+ + +Actor: + +DHCP Server + + + +Role: + +Maintains internal configuration files. + + + +Actor: + +Site Administrator + + + +Role: + +Updates configuration information to add, modify, and remove descriptions of clients and servers. + + + +Actor: + +Service Staff + + + +Role: + +Provides initial configuration requirements for many devices when installing a new network, and for individual devices when installing or modifying a single device. + + + +
+
+ Referenced Standards + + None +
+
+
+ Find and Use DHCP Server + +
+ Scope + + This is the support for the normal startup process. The DHCP client system boots up, and very early in the booting process it finds DHCP servers, selects one of the DHCP servers to be its server, queries that server to obtain a variety of information, and continues DHCP client self-configuration using the results of that query. DHCP servers may optionally provide a variety of information, such as server locations, normal routes. This transaction identifies what information shall be provided by a compliant DHCP server, and identifies what information shall be requested by a compliant DHCP client. A compliant DHCP server in not required to provide this optional information. +
+
+ Use Case Roles + + +
+ Find and Use DHCP Server + + + + + + +
+
+ + +Actor: + +DHCP Server + + + +Role: + +Responds to DHCP acquisition queries. Multiple actors may exist. The DHCP client will select one. + + + +Actor: + +DHCP client + + + +Role: + +Queries for DHCP Servers. Selects one responding server. + + + +
+
+ Referenced Standards + + RFC2131 DHCP Protocol + RFC2132 DHCP Options + RFC2563 Auto Configuration control +
+
+ Interaction Diagram + + +
+ DHCP Interactions + + + + + + +
+
+ The DHCP client shall comply with RFC2131 (DHCP Protocol), RFC2132 (DHCP Options), RFC2563 (Auto Configuration Control), and their referenced RFCs. + The DHCP client shall query for available DHCP servers. It shall select the DHCP server to use. + The DHCP client shall query for an IP assignment. The DHCP Server shall determine the IP parameters in accordance with the current DHCP configuration, establish a lease for these parameters, and respond with this information. (See below for lease maintenance and expiration.) The DHCP client shall apply these parameters to the TCP/IP stack. The DHCP client shall establish internal lease maintenance activities. + The DHCP client shall query for the optional information listed in when required by additional profiles used by the client system. If the DHCP server does not provide this information, the default values shall be used by the DHCP client. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
DHCP Parameters
+ DHCP Option + + Description + + Default +
+ NTP + + List of NTP servers + + Empty list +
+ DNS + + List of DNS servers + + Empty list +
+ Router + + Default router + + Empty list +
+ Static routes + + + Nil +
+ Hostname + + + Requested machine name +
+ Domain name + + + Nil +
+ Subnet mask + + + Derived from network value +
+ Broadcast address + + + Derived from network value +
+ Default router + + + Nil +
+ Time offset + + + Site configurable +
+ MTU + + + Hardware dependent +
+ Auto-IP permission + + + From NVRAM +
+ The DHCP client shall make this information available for other actors within the DHCP client machine. +
+
+
+ Maintain Lease + +
+ Scope + + The DHCP client normally maintains the IP lease in compliance with the RFCs. Sometimes the server will not renew the lease. Non-renewal is usually part of network service operations. The loss of the IP lease requires connections using that IP address to cease. +
+
+ Use Case Roles + + +
+ Maintain Lease + + + + + + +
+
+ + +Actor: + +DHCP client + + + +Role: + +Deals with lease renewal and expiration. + + + +Actor: + +DHCP Server + + + +Role: + +Renewing or deliberately letting leases expire (sometimes done as part of network service operations). + + + +
+
+ Referenced Standards + + RFC2131 DHCP Protocol + RFC2132 DHCP Options +
+
+ Normal Interaction + + The DHCP client shall maintain a lease on the IP address in accordance with the DHCP protocol as specified in RFC2131 and RFC2132. There is a possibility that the DHCP Server may fail, or may choose not to renew the lease. + In the event that the DHCP lease expires without being renewed, any still active DICOM connections may be aborted (AP-Abort). + + There is usually a period (typically between several minutes and several days) between the request for lease extension and actual expiration of the lease. The application might take advantage of this to perform a graceful association release rather than the abrupt shutdown of an AP-Abort. + +
+
+
+ DDNS Coordination + +
+ Scope + + DHCP servers may coordinate their IP and hostname assignments with a DNS server. This permits dynamic assignment of IP addresses without interfering with access to DHCP Clients by other systems. The other systems utilize the agreed hostname (which DHCP can manage and provide to the client) and obtain the current IP address by means of DNS lookup. + A DHCP Server is in compliance with this optional part of the Basic Network Address Management Profile profile if it maintains and updates the relevant DNS server so as to maintain the proper hostname/IP relationships in the DNS database. +
+
+ Use Case Roles + + +
+ DDNS Coordination + + + + + + +
+
+ + +Actor: + +DHCP Server + + + +Role: + +Responded to DHCP acquisition queries and assigned IP address to client. + + + +Actor: + +DNS Server + + + +Role: + +Maintains the DNS services for the network. + + + +
+
+ Referenced Standards + + RFC2136 Dynamic Updates in the Domain Name System +
+
+ Basic Course of Events + + After the DHCP server has assigned an IP address to a DHCP client, the DHCP server uses DDNS to inform the DNS server that the hostname assigned to the DHCP client has been given the assigned IP address. The DNS Server updates the DNS database so that subsequent DNS queries for this hostname are given the assigned IP address. When the lease for the IP address expires without renewal, the DHCP server informs the DNS server that the IP address and hostname are no longer valid. The DNS server removes them from the DNS database. +
+
+
+ DHCP Security Considerations (Informative) + + The Basic Network Address Management Profile Profile has two areas of security concerns: + + +Protection against denial of service attacks against the DHCP client/server traffic. + + +Protection against denial of service attacks against the DHCP server to DDNS server update process. + + + The Basic Network Address Management Profile Profile should not be used outside a secured environment. At a minimum there should be: + + +Firewall and or router protections to ensure that only approved hosts are used for DHCP and DNS services. + + +Agreements for VPN and other access should require that DNS clients on the hospital network use only approved DHCP or DNS servers over the VPN. + + + Other network security procedures such as automated intrusion detection may be appropriate in some environments. Security features beyond this minimum should be established by the local security policy and are beyond the scope of DICOM. + The purpose of the selected security is to limit the scope of the threat to insider attacks. The DHCP and DNS systems disclose only hostnames and IP addresses, so there is little concern about eavesdropping. The protections are to limit the exposure to denial of service attacks by counterfeit servers or clients. The specific DNS security extensions are described in . This profile does not utilize the DHCP security extensions because they provide very limited added security and the attacks are insider denial of service attacks. Intrusion detection and other network level protection mechanisms are the most effective next level of protections for the DHCP process. + The DNS update is optional in this profile to accommodate the possibility that the DHCP server and DNS server cannot reach a mutually acceptable security process. Support of this option may require support of the DNS security protocols that are in the process of development. See for a discussion of the DNS security profile standards and drafts. +
+
+ DHCP Implementation Considerations (Informative) + + The DHCP configuration file can be a very useful form of documentation for the local network hardware configuration. It can be prepared in advance for new installations and updated as clients are added. Including information for all machines, including those that do not utilize DHCP, avoids accidental IP address conflicts and similar errors. + Most DHCP servers have a configuration capability that permits control of the IP address and other information provided to the client. These controls can pre-allocate a specific IP address, etc. to a machine based on the requested machine name or MAC address. These pre-allocated IP addresses then ensure that these specific machines are always assigned the same IP address. Legacy systems that do not utilize DNS can continue to use fixed tables with IP addresses when the DHCP server has pre-allocated the IP addresses for those services. +
+
+ Conformance + + The Conformance Statement for an LDAP Client shall describe its use of LDAP to configure the local AE titles. Any conformance to the Update LDAP Server option shall be specified, together with the values for all component object attributes in the update sent to the LDAP Server. Any use of LDAP to configure the remote device addresses and capabilities shall be described. The LDAP queries used to obtain remote device component object attributes shall be specified. + + In particular, use of LDAP to obtain the AE Title, TCP port, and IP address for specific system actors (e.g., an Image Archive, or a Performed Procedure Step Manager) should be detailed, as well as how the LDAP information for remote devices is selected for operational use. + + +
+
+
+ + Time Synchronization Profiles + +
+ Basic Time Synchronization Profile + + The Basic Time Synchronization Profile defines services to synchronize the clocks on multiple computers. It employs the Network Time Protocol (NTP) services that have been used for this purpose by many other disciplines. NTP permits synchronization to a local server that provides a local time source, and synchronization to a variety of external time services. The accuracy and precision controls are not explicitly part of the protocol. They are determined in large part by the selection of clock hardware and network topology. + An extensive discussion of implementation strategies for NTP can be found at http://www.ntp.org. + The Basic Time Synchronization Profile applies to the actors DHCP Client, DHCP Server, SNTP Client, NTP Client and NTP Server. The mandatory and optional transactions are described in the table and sections below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Basic Time Synchronization Profile
+ Actor + + Transaction + + Optionality + + Section +
+ NTP Server + + Maintain Time + + M + + G.1.2 +
+ + Find NTP Servers + + O + + G.1.1 +
+ NTP Client + + Maintain Time + + M + + G.1.2 +
+ + Find NTP Servers + + O + + G.1.1 +
+ SNTP Client + + Maintain Time + + M + + G.1.2 +
+ DHCP Server + + Find NTP Servers + + O + + G.1.1 +
+ DCHP Client + + Find NTP Servers + + M + + G.1.1 +
+
+ Find NTP Servers + + The optional NTP protocol elements for NTP autoconfiguration and NTP autodiscovery can significantly simplify installation. The NTP specification for these is defined such that they are truly optional for both client and server. In the event that a client cannot find an NTP server automatically using these services, it can use the DHCP optional information or manually configured information to find a server. Support for these services is recommended but not mandatory. + This transaction exists primarily as a means of documenting whether particular models of equipment support the automatic discovery. This lets installation and operation plan their DHCP and equipment installation procedures in advance. +
+ Scope + + This applies to any client that needs the correct time, or that needs to have its time stamps synchronized with those of another system. The accuracy of synchronization is determined by details of the configuration and implementation of the network and NTP servers at any specific site. + Both the NTP and SNTP clients shall utilize the NTP server information if it is provided by DHCP and NTP services have not been found using autodiscovery. Manual configuration shall be provided as a backup. Autodiscovery or DHCP are preferred. +
+
+ Use Case Roles + + +
+ Find NTP Servers + + + + + + +
+
+ + +DHCP Server + +Provides UTC offset, provides list of NTP servers + + + +DHCP Client + +Receives UTC offset and list of NTP servers + + + +NTP Client + +Maintains client clock + + + +SNTP Client + +Maintains client clock + + + +NTP Servers + +External time servers. These may have connections to other time servers, and may be synchronized with national time sources. + + + +
+
+ Referenced Standards + + RFC1305 Network Time Protocol (NTP) standard specification + RFC2030 Simple NTP +
+
+ Basic Course of Events. + + The DHCP server may have provided a list of NTP servers or one may be obtained through optional NTP discovery mechanisms. If this list is empty and no manually configured NTP server address is present, the client shall select its internal clock as the time source (see below). If the list is not empty, the client shall attempt to maintain time synchronization with all those NTP servers. The client may attempt to use the multi-cast, manycast, and broadcast options as defined in RFC1305. It shall utilize the point to point synchronization option if these are not available. The synchronization shall be in compliance with either RFC1305 (NTP) or RFC2030 (SNTP). + If the application requires time synchronization of better than 1s mean error, the client should use NTP. SNTP cannot ensure a more accurate time synchronization. + The DHCP server may have provided a UTC offset between the local time at the machine and UTC. If this is missing, the UTC offset will be obtained in a device specific manner (e.g., service, CMOS). If the UTC offset is provided, the client shall use this offset for converting between UTC and local time. +
+
+ Alternative Paths + + If there is no UTC offset information from the DHCP server, then the NTP client will use its preset or service set UTC offset. + If there is no NTP time server, then the NTP client will select its internal battery clock as the source of UTC. These may have substantial errors. This also means that when there are multiple systems but no NTP source, the multiple systems will not attempt to synchronize with one another. +
+
+ Assumptions + + The local battery clock time is set to UTC, or the local operating system has proper support to manage both battery clock time, NTP clock time, and system clock time. The NTP time is always in UTC. +
+
+ Postconditions + + The client will remain synchronized with its selected time source. In an environment with one or more NTP servers, this will be good time synchronization. In the absence of NTP servers, the selected source will be the internal client clock, with all its attendant errors. +
+
+
+ Maintain Time + +
+ Scope + + This applies to any client that needs the correct time, or that needs to have its time stamps synchronized with those of another system. The accuracy of synchronization is determined by details of the configuration and implementation of the network and NTP servers at any specific site. +
+
+ Use Case Roles + + +
+ Maintain Time + + + + + + +
+
+ + +NTP/SNTP Client + +Maintains client clock + + + +NTP Servers + +External time servers. These may have connections to other time servers, and may be synchronized with national time sources. + + + +
+
+ Referenced Standards + + RFC1305 Network Time Protocol (NTP) standard specification + RFC2030 Simple NTP +
+
+ Basic Course of Events. + + All the full detail is in RFC1305 and RFC2030. The most common and mandatory minimum mode for NTP operation establishes a ping pong of messages between client and servers. The client sends requests to the servers, which fill in time related fields in a response, and the client performs optimal estimation of the present time. The RFCs deal with issues of lost messages, estimation formulae, etc. Once the clocks are in synchronization these ping pong exchanges typically stabilize at roughly 1000 second intervals. + The client machine typically uses the time estimate to maintain the internal operating system clock. This clock is then used by applications that need time information. This approach eliminates the application visible difference between synchronized and unsynchronized time. The RFCs provide guidance on proper implementations. +
+
+
+ NTP Security Considerations (Informative) + + The Basic Time Synchronization profile should not be used outside a secured environment. At a minimum there should be: + + +Firewall and or router protections to ensure that only approved hosts are used for NTP services. + + +Agreements for VPN and other access should require that use only approved NTP servers over the VPN. + + + This limits the risks to insider denial of service attacks. The service denial is manipulation of the time synchronization such that systems report the incorrect time. The NTP protocols incorporate secure transaction capabilities that can be negotiated. This profile assumes that the above protections are sufficient and does not require support of secure transactions, but they may be supported by an implementation. The SNTP client does not support the use of secured transactions. + Sites with particular concerns regarding security of external network time sources may choose to utilize a GPS or radio based time synchronization. Note that when selecting GPS and radio time sources, care must be taken to establish the accuracy and stability provided by the particular time source. The underlying time accuracy of GPS and radio sources is superb, but some receivers are intended for low accuracy uses and do not provide an accurate or stable result. +
+
+ NTP Implementation Considerations (Informative) + + NTP servers always support both NTP and SNTP clients. The difference is one of synchronization accuracy, not communications compatibility. Although in theory both NTP and SNTP clients could run at the same time on a client this is not recommended. The SNTP updates will simply degrade the time accuracy. When other time protocol clients, such as IRIG, are also being used these clients must be coordinated with the NTP client to avoid synchronization problems. + RFC1305 includes specifications for management of intermittent access to the NTP servers, broken servers, etc. The NTP servers do not need to be present and operational when the NTP process begins. NTP supports the use of multiple servers to provide backup and better accuracy. RFC1305 specifies the mechanisms used by the NTP client. The site www.ntp.org provides extensive guidance and references regarding the most effective configurations for backups and multiple server configurations. + The local battery clock and client operating system must be properly UTC aware. NTP synchronization is in UTC. This can be a source of confusion because some computers are configured with their hardware clocks set to local time and the operating system set (incorrectly) to UTC. This is a common error that only becomes apparent when the devices attempt to synchronize clocks. +
+
+ Conformance + + The Conformance Statement for the NTP Server and NTP Client shall state whether secure transactions are supported. + The Conformance Statement for the NTP Server shall state whether it is also an NTP Client. + +
+
+
+ + Application Configuration Management Profiles + +
+ Application Configuration Management Profile + + The Application Configuration Management Profile applies to the actors LDAP Server, LDAP Client, and DNS Server. The mandatory and optional transactions are described in the table and sections below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Application Configuration Management Profiles
+ Actor + + Transaction + + Optionality + + Section +
+ LDAP Server + + Query LDAP Server + + M + + H.1.4.2 +
+ + Update LDAP Server + + O + + H.1.4.3 +
+ + Maintain LDAP Server + + M + + H.1.4.4 +
+ LDAP Client + + Find LDAP Server + + M + + H.1.4.1 +
+ + Query LDAP Server + + M + + H.1.4.2 +
+ + Update LDAP Server + + O + + H.1.4.3 +
+ DNS Server + + Find LDAP Server + + M + + H.1.4.1 +
+
+ Data Model Component Objects + The normative definition of the schema can be found in . This section gives additional informative descriptions of the objects and information defined in that schema and makes normative statements regarding DICOM system behavior. + The Application Configuration Data Model has the following component objects: + + + Device + + The description of the device + + + + Network AE + + The description of the network application entity + + + + Network Connection + + The description of the network interface + + + + Transfer Capability + + The description of the SOP classes and syntaxes supported by a Network AE. + + + + +
+ Application Configuration Data Model + + + + + + +
+
+ In addition there are a number of other objects used in the LDAP schema (see and ) : + + + DICOM Configuration Root + + The root of DICOM Configuration Hierarchy + + + + DICOM Devices Root + + The root of the DICOM Devices Hierarchy + + + + DICOM Unique AE-Title Registry Root + + The root of the Unique DICOM AE-Title Registry + + + + DICOM Unique AE Title + + A unique AE Title within the AE Title Registry + + + + LDAP permits extensions to schema to support local needs (i.e., an object may implement a single structural and multiple auxiliary LDAP classes). DICOM does not mandate client support for such extensions. Servers may support such extensions for local purposes. DICOM Clients may accept or ignore extensions and shall not consider their presence an error. +
+ Device + + The "device" is set of components organized to perform a task rather than a specific physical instance. For simple devices there may be one physical device corresponding to the Data Model device. But for complex equipment there may be many physical parts to one "device". + The "device" is the collection of physical entities that supports a collection of Application Entities. It is uniquely associated with these entities and vice versa. It is also uniquely associated with the network connections and vice versa. In a simple workstation with one CPU, power connection, and network connection the "device" is the workstation. + An example of a complex device is a server built from a network of multiple computers that have multiple network connections and independent power connections. This would be one device with one application entity and multiple network connections. Servers like this are designed so that individual component computers can be replaced without disturbing operations. The Application Configuration Data Model does not describe any of this internal structure. It describes the network connections and the network visible Application Entities. These complex devices are usually designed for very high availability, but in the unusual event of a system shutdown the "device" corresponds to all the parts that get shut down. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes of Device Object
+ Information Field + + Multiplicity + + Description +
+ Device Name + + 1 + + A unique name (within the scope of the LDAP database) for this device. It is restricted to legal LDAP names, and not constrained by DICOM AE Title limitations. +
+ Description + + 0..1 + + Unconstrained text description of the device. +
+ Manufacturer + + 0..1 + + Should be the same as the value of Manufacturer (0008,0070) in SOP instances created by this device. +
+ Manufacturer Model Name + + 0..1 + + Should be the same as the value of Manufacturer Model Name (0008,1090) in SOP instances created by this device. +
+ Software Version + + 0..N + + Should be the same as the values of Software Versions (0018,1020) in SOP instances created by this device. +
+ Station Name + + 0..1 + + Should be the same as the value of Station Name (0008,1010) in SOP instances created by this device. +
+ Device Serial Number + + 0..1 + + Should be the same as the value of Device Serial Number (0018,1000) in SOP instances created by this device. +
+ Primary Device Type + + 0..N + + Represents the kind of device and is most applicable for acquisition modalities. Types should be selected from the list of code values (0008,0100) for when applicable. +
+ Institution Name + + 0..N + + Should be the same as the value of Institution Name (0008,0080) in SOP Instances created by this device. +
+ Institution Address + + 0..N + + Should be the same as the value of Institution Address (0008,0081) attribute in SOP Instances created by this device. +
+ Institutional Department Name + + 0..N + + Should be the same as the value of Institutional Department Name (0008,1040) in SOP Instances created by this device. +
+ Issuer of Patient ID + + 0..1 + + Default value for the Issuer of Patient ID (0010,0021) for SOP Instances created by this device. May be overridden by the values received in a worklist or other source. +
+ Related Device Reference + + 0..N + + The DNs of related device descriptions outside the DICOM Configuration hierarchy. Can be used to link the DICOM Device object to additional LDAP objects instantiated from other schema and used for separate administrative purposes. +
+ Authorized Node Certificate Reference + + 0..N + + The DNs for the certificates of nodes that are authorized to connect to this device. The DNs need not be within the DICOM configuration hierarchy. +
+ This Node Certificate Reference + + 0..N + + The DNs of the public certificate(s) for this node. The DNs need not be within the DICOM configuration hierarchy. +
+ Vendor Device Data + + 0..N + + Device specific vendor configuration information +
+ Installed + + 1 + + Boolean to indicate whether this device is presently installed on the network. (This is useful for pre-configuration, mobile vans, and similar situations.) +
+ The "Authorized Node Certificate Reference" is intended to allow the LDAP server to provide the list of certificates for nodes that are authorized to communicate with this device. These should be the public certificates only. This list need not be complete. Other network peers may be authorized by other mechanisms. + The "This Node Certificate Reference" is intended to allow the LDAP server to provide the certificate(s) for this node. These may also be handled independently of LDAP. + + A device may have multiple Primary Device Type entries. It may be a multifunctional device, e.g., combined PET and CT. It may be a cascaded device, e.g., image capture and ultrasound. + + + + + + + + + + + + + + + + + + + + + + +
Child Objects of Device Object
+ Information Field + + Multiplicity + + Description +
+ Network Application Entity + + 1..N + + The application entities available on this device (see ) +
+ Network Connection + + 1..N + + The network connections for this device (see ) +
+
+
+ Network Application Entity + + A Network AE is an application entity that provides services on a network. A Network AE will have the same functional capability regardless of the particular network connection used. If there are functional differences based on selected network connection, then these are separate Network AEs. If there are functional differences based on other internal structures, then these are separate Network AEs. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes of Network AE Object
+ Information Field + + Multiplicity + + Description +
+ AE Title + + 1 + + Unique AE title for this Network AE +
+ Description + + 0..1 + + Unconstrained text description of the application entity. +
+ Vendor Data + + 0..N + + AE specific vendor configuration information +
+ Application Cluster + + 0..N + + Locally defined names for a subset of related applications. E.g. "neuroradiology". +
+ Preferred Called AE Title + + 0..N + + AE Title(s) that are preferred for initiating associations. +
+ Preferred Calling AE Title + + 0..N + + AE Title(s) that are preferred for accepting associations. +
+ Association Acceptor + + 1 + + A Boolean value. True if the Network AE can accept associations, false otherwise. +
+ Association Initiator + + 1 + + A Boolean value. True if the Network AE can accept associations, false otherwise. +
+ Network Connection Reference + + 1..N + + The DNs of the Network Connection objects for this AE +
+ Supported Character Set + + 0..N + + The Character Set(s) supported by the Network AE for data sets it receives. The value shall be selected from the Defined Terms for Specific Character Set (0008,0005) in . If no values are present, this implies that the Network AE supports only the default character repertoire(ISO IR 6). +
+ Installed + + 0..1 + + A Boolean value. True if the AE is installed on network. If not present, information about the installed status of the AE is inherited from the device +
+ The "Application Cluster" concept provides the mechanism to define local clusters of systems. The use cases for Configuration Management require a "domain" capability for DICOM applications that would be independent of the network topology and administrative domains that are used by DNS and other TCP level protocols. The Application Cluster is multi-valued to permit multiple clustering concepts for different purposes. It is expected to be used as part of a query to limit the scope of the query. + The "Preferred Called AE Title" concept is intended to allow a site administrator to define a limited default set of AEs that are preferred for use as communication partners when initiating associations. This capability is particularly useful for large centrally administered sites to simplify the configuration possibilities and restrict the number of configured AEs for specific workflow scenarios. For example, the set of AEs might contain the AE Titles of assigned Printer, Archive, RIS and QA Workstations so that the client device could adapt its configuration preferences accordingly. The "Preferred Called AE Title" concept does not prohibit association initiation to unlisted AEs. Associations to unlisted AEs can be initiated if necessary. + The "Preferred Calling AE Title" concept is intended to allow a site administrator to define a default set of AEs that are preferred when accepting assocations. The "Preferred Calling AE Title" concept does not prohibit accepting associations from unlisted AEs. + The "Network Connection Reference" is a link to a separate Network Connection object. The referenced Network Connection object is a sibling the AE object (i.e., both are children of the same Device object). + + + + + + + + + + + + + + + + +
Child Objects of Network AE Object
+ Information Field + + Multiplicity + + Description +
+ Transfer Capability + + 1..N + + The Transfer Capabilities for this Network AE. See + +
+
+
+ Network Connection + + The "network connection" describes one TCP port on one network device. This can be used for a TCP connection over which a DICOM association can be negotiated with one or more Network AEs. It specifies the hostname and TCP port number. A network connection may support multiple Network AEs. The Network AE selection takes place during association negotiation based on the called and calling AE-titles. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes of Network Connection Object
+ Information Field + + Multiplicity + + Description +
+ Common Name + + 0..1 + + An arbitrary name for the Network Connections object. Can be a meaningful name or any unique sequence of characters. Can be used as the RDN. + + The "cn" attribute type is a basic LDAP defined type and is a synonym for Common Name. + +
+ Hostname + + 1 + + This is the DNS name for this particular connection. This is used to obtain the current IP address for connections. Hostname must be sufficiently qualified to be unambiguous for any client DNS user. +
+ Port + + 0..1 + + The TCP port that the AE is listening on. (This may be missing for a network connection that only initiates associations.) +
+ TLS CipherSuite + + 0..N + + The TLS CipherSuites that are supported on this particular connection. TLS CipherSuites shall be described using an RFC2246 string representation (e.g., "TLS_RSA_WITH_RC4_128_SHA") +
+ Installed + + 0..1 + + A Boolean value. True if the Network Connection is installed on the network. If not present, information about the installed status of the Network Connection is inherited from the device. +
+ Inclusion of a TLS CipherSuite in a Network Connection capable of accepting associations implies that the TLS protocol must be used to successfully establish an association on the Network Connection. + A single Network AE may be available on multiple network connections. This is often done at servers for availability or performance reasons. For example, at a hospital where each floor is networked to a single hub per floor, the major servers may have direct connections to each of the hubs. This provides better performance and reliability. If the server does not change behavior based on the particular physical network connection, then it can be described as having Network AEs that are available on all of these multiple network connections. A Network AE may also be visible on multiple TCP ports on the same network hardware port, with each TCP port represented as a separate network connection. This would allow, e.g., a TLS-secured DICOM port and a classical un-secured DICOM port to be supported by the same AE. +
+
+ Transfer Capabilities + + Each Network AE object has one or more Transfer Capabilities. Each transfer capability specifies the SOP class that the Network AE can support, the mode that it can utilize (SCP or SCU), and the Transfer Syntax(es) that it can utilize. A Network AE that supports the same SOP class in both SCP and SCU modes will have two Transfer Capabilities objects for that SOP class. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Attributes of Transfer Capability Object
+ Information Field + + Multiplicity + + Description +
+ Common Name + + 0..1 + + An arbitrary name for the Transfer Capability object. Can be a meaningful name or any unqiue sequence of characters. Can be used as the RDN. +
+ SOP Class + + 1 + + SOP Class UID +
+ Role + + 1 + + Either "SCU" or "SCP" +
+ Transfer Syntax + + 1..N + + The transfer syntax(es) that may be requested as an SCU or that are offered as an SCP. +
+
+
+ DICOM Configuration Root + + This structural object class represents the root of the DICOM Configuration Hierarchy. Only a single object of this type should exist within an organizational domain. Clients can search for an object of this class to locate the root of the DICOM Configuration Hierarchy. + + + + + + + + + + + + + + + + + + + + + +
Attributes of the DICOM Configuration Root Object
+ Information Field + + Multiplicity + + Description +
+ Common Name + + 1 + + The Name for the Configuration Root. Should be used as the RDN. The name shall be "DICOM Configuration". +
+ Description + + 0..1 + + Unconstrained text description. +
+ + + + + + + + + + + + + + + + + + + + + +
Child Objects of DICOM Configuration Root Object
+ Information Field + + Multiplicity + + Description +
+ Devices Root + + 1 + + The root of the DICOM Devices Hierarchy +
+ Unique AE Titles Registry Root + + 1 + + The root of the Unique AE Titles Registry +
+
+
+ Devices Root + + This structural object class represents the root of the DICOM Devices Hierarchy. Only a single object of this type should exist as a child of DICOM Configuration Root. Clients can search for an object of this class to locate the root of the DICOM Devices Hierarchy. + + + + + + + + + + + + + + + + + + + + + +
Attributes of the Devices Root Object
+ Information Field + + Multiplicity + + Description +
+ Common Name + + 1 + + The Name for the Devices Root. Should be used as the RDN. The name shall be "Devices". +
+ Description + + 0..1 + + Unconstrained text description. +
+ + + + + + + + + + + + + + + + +
Child Objects of Devices Root Object
+ Information Field + + Multiplicity + + Description +
+ Device + + 0..N + + The individual devices installed within this organizational domain. +
+
+
+ Unique AE Titles Registry Root + + This structural object class represents the root of the Unique AE-Titles Registry Hierarchy. Only a single object of this type should exist as a child of the DICOM Configuration Root. Clients can search for an object of this class to locate the root of the Unique AE Titles Registry. + + + + + + + + + + + + + + + + + + + + + +
Attributes of the Unique AE Titles Registry Root Object
+ Information Field + + Multiplicity + + Description +
+ Common Name + + 1 + + The Name for the Unique AE Titles Registry Root. Should be used as the RDN. The name shall be "Unique AE Titles Registry". +
+ Description + + 0..1 + + Unconstrained text description. +
+ + + + + + + + + + + + + + + + +
Child Objects of Unique AE Titles Registry Root Object
+ Information Field + + Multiplicity + + Description +
+ Unique AE Title + + 0..N + + The unique AE Titles installed within this organizational domain (see ) +
+
+
+ Unique AE Title + + This structural object class represents a Unique Application Entity Title. Objects of this type should only exist as children of the Unique AE-Titles Registry Root. The sole purpose of this object class is to enable allocation of unique AE Titles. All operational information associated with an AE Title is maintained within a separate Network AE object. + + + + + + + + + + + + + + + + +
Attributes of the Unique AE Title Object
+ Information Field + + Multiplicity + + Description +
+ AE Title + + 1 + + The Unique AE Titles. +
+
+
+
+ Application Configuration Data Model Hierarchy + + The LDAP structure is built upon a hierarchy of named objects. This hierarchy can vary from site to site. The DICOM configuration management function needs to find its objects within this hierarchy in a predictable manner. For this reason, three specific object classes are defined for the three objects at the top of the DICOM hierarchy. These three object classes must not be used in this tree relationship anywhere else in the LDAP hierarchy. + The DICOM portion of the hierarchy shall begin at a root object of class dicomConfigurationRoot with a Common Name of "DICOM Configuration". Below this object shall be two other objects: + + +An object of class dicomDevicesRoot with a Common Name of "Devices". This is the root of the tree of objects that correspond to the Application Configuration Data Model structure of . + + +An object of class dicomUniqueAETitlesRegistryRoot with a common name of "Unique AE Titles Registry". This is the root of a flat tree of objects. Each of these objects is named with one of the AE titles that are presently assigned. This is the mechanism for finding available AE titles. + + + The three object classes dicomConfigurationRoot, dicomDevicesRoot, and dicomUniqueAETitleRegistryRoot are used by LDAP clients to establish the local root of the DICOM configuration information within an LDAP hierarchy that may be used for many other purposes. + + During system startup it is likely that the DICOM configuration application will do an LDAP search for an entry of object class dicomConfigurationRoot and then confirm that it has the dicomDevicesRoot and dicomUniqueAETitlesRegistryRoot entries directly below it. When it finds this configuration, it can then save the full location within the local LDAP tree and use that as the root of the DICOM tree. + + The objects underneath the dicomUniqueAETitlesRegistryRoot are used to provide the uniqueness required for DICOM AE-titles. The dicomUniqueAETitle objects have a single attribute representing a unique AE Title. When a new AE-Title is required, a tentative new name is selected. The new name is reserved by using the LDAP create facility to create an object of class dicomUniqueAETitle with the new name under the AE-Title object. If this name is already in use, the create will fail. Otherwise, this reserves the name. LDAP queries can be used to obtain the list of presently assigned AE-titles by obtaining the list of all names under the dicomUniqueAETitlesRegistryRoot object. + +
+ DICOM Configuration Hierarchy + + + + + + +
+
+ + + + LDAP uses a root and relative hierarchical naming system for objects. Every object name is fully unique within the full hierarchy. This means that the names of the objects beneath "Unique AE Titles Registry" will be unique. It also means that the full names of Network AEs and Connections will be within their hierarchy context. E.g., the DN for one of the Network AEs in would be: + + +dicomAETitle=CT_01, dicomDeviceName=Special Research CT, cn=Devices, cn=DICOM Configuration, o=Sometown Hospital + + + + +In theory, multiple independent DICOM configuration hierarchies could exist within one organization. The LDAP servers in such a network should constrain local device accesses so that DICOM configuration clients have only one DICOM Configuration Hierarchy visible to each client. + + +The merger of two organizations will require manual configuration management to merge DICOM Configuration hierarchies. There are likely to be conflicts in AE-titles, roles, and other conflicts. + + + +
+
+ LDAP Schema For Objects and Attributes + + The individual LDAP attribute information is summarized in the comments at the beginning of the schema below. The formal definition of the objects and the attributes is in the schema below. This schema may be extended by defining an additional schema that defines auxiliary classes, sub-classes derived from this schema, or both. + The formal LDAP schema for the Application Configuration Data Model and the DICOM Configuration Hierarchy is: + + +
+
+ Transactions + +
+ Find LDAP Server + +
+ Scope + + The RFC2782 + A DNS RR for specifying the location of services (DNS SRV)specifies a mechanism for requesting the names and rudimentary descriptions for machines that provide network services. The DNS client requests the descriptions for all machines that are registered as offering a particular service name. In this case the service name requested will be "LDAP". The DNS server may respond with multiple names for a single request. +
+
+ Use Case Roles + + +
+ Find LDAP Server + + + + + + +
+
+ + +DNS Server + +Provides list of LDAP servers + + + +LDAP Client + +Requests list of LDAP servers + + + +
+
+ Referenced Standards + + RFC2181 Clarifications to the DNS Specification + RFC2219 Use of DNS Aliases for Network Services + RFC2782 A DNS RR for specifying the location of services (DNS SRV) + other RFC's are included by reference from RFC2181, RFC2219, and RFC2782. +
+
+ Interaction Diagram + + +
+ Select LDAP Server + + + + + + +
+
+ The DNS client shall request a list of all the LDAP servers available. It will use the priority, capacity, and location information provided by DNS to select a server. (RFC2782 recommends the proper use of these parameters.) It is possible that there is no LDAP server, or that the DNS server does not support the SRV RR request. + + + + Multiple LDAP servers providing access to a common replicated LDAP database is a commonly supported configuration. This permits LDAP servers to be located where appropriate for best performance and fault tolerance. The DNS server response information provides guidance for selecting the most appropriate server. + + + There may also be multiple LDAP servers providing different databases. In this situation the client may have to examine several servers to find the one that supports the DICOM configuration database. Similarly a single LDAP server may support multiple base DNs, and the client will need to check each of these DNs to determine which is the DICOM supporting tree. + + + +
+
+ Alternative Paths + + The client may have a mechanism for manual default selection of the LDAP server to be used if the DNS server does not provide an LDAP server location. +
+
+
+ Query LDAP Server + +
+ Scope + + The RFC2251 "Lightweight Directory Access Protocol (v3) " specifies a mechanism for making queries of a database corresponding to an LDAP schema. The LDAP client can compose requests in the LDAP query language, and the LDAP server will respond with the results for a single request. +
+
+ Use Case Roles + + +
+ Query LDAP Server + + + + + + +
+
+ + +LDAP Server + +Provides query response + + + +LDAP Client + +Requests LDAP information + + + +
+
+ Referenced Standards + + RFC2251 Lightweight Directory Access Protocol (v3). LDAP support requires compliance with other RFC's invoked by reference. +
+
+ Interaction Description + + The LDAP client may make a wide variety of queries and cascaded queries using LDAP. The LDAP client and server shall support the Application Configuration Data Model . + + Multiple LDAP servers providing access to a common replicated LDAP database is a commonly supported configuration. This permits LDAP servers to be located where appropriate for best performance and fault tolerance. The replications rules chosen for the LDAP servers affect the visible data consistency. LDAP permits inconsistent views of the database during updates and replications. + +
+
+
+ Update LDAP Server + +
+ Scope + + The RFC2251 "Lightweight Directory Access Protocol (v3) " specifies a mechanism for making updates to a database corresponding to an LDAP schema. The LDAP client can compose updates in the LDAP query language, and the LDAP server will respond with the results for a single request. Update requests may be refused for security reasons. +
+
+ Use Case Roles + + +
+ Update LDAP Server + + + + + + +
+
+ + +LDAP Server + +Maintains database + + + +LDAP Client + +Updates LDAP information + + + +
+
+ Referenced Standards + + RFC2251 Lightweight Directory Access Protocol (v3). LDAP support requires compliance with other RFC's invoked by reference. +
+
+ Interaction Description + + The LDAP client may make a request to update the LDAP database. The LDAP client shall support the data model described above. The LDAP server may choose to refuse the update request for security reasons. If the LDAP server permits update requests, is shall support the data model described above. + + Multiple LDAP servers providing access to a common replicated LDAP database is a commonly supported configuration. This permits LDAP servers to be located where appropriate for best performance and fault tolerance. Inappropriate selection of replication rules in the configuration of the LDAP server will result in failure for AE-title uniqueness when creating the AE-titles objects. + +
+
+ Special Update For Network AE Creation + + The creation of a new Network AE requires special action. The following steps shall be followed: + + +A tentative AE title shall be selected. Various algorithms are possible, ranging from generating a random name to starting with a preset name template and incrementing a counter field. The client may query the Unique AE Titles Registry sub-tree to obtain the complete list of names that are presently in use as part of this process. + + +A new Unique AE Title object shall be created in the Unique AE Titles Registry portion of the hierarchy with the tentative name. The LDAP server enforces uniqueness of names at any specific point in the hierarchy. + + +If the new object creation was successful, this shall be the AE Title used for the new Network AE. + + +If the new object creation fails due to non-unique name, return to a) and select another name. + + +
+
+
+ Maintain LDAP Server + + The LDAP server shall support a separate manual or automated means of maintaining the LDAP database contents. The LDAP server shall support the RFC2849 file format mechanism for updating the LDAP database. The LDAP Client or service installation tools shall provide RFC2849 formatted files to update LDAP server databases manually. The LDAP server may refuse client network updates for security reasons. If this is the case, then the maintenance process will be used to maintain the LDAP database. + The manual update procedures are not specified other than the requirement above that at least the minimal LDAP information exchange file format from RFC2849 be supported. The exact mechanisms for transferring this information remain vendor and site specific. In some situations, for example the creation of AE-titles, a purely manual update mechanism may be easier than exchanging files. + The conformance statement shall document the mechanisms available for transferring this information. Typical mechanisms include: + + +floppy disk + + +CD-R + + +SSH + + +Secure FTP + + +FTP + + +email + + +HTTPS + + + + + + There are many automated and semi-automatic tools for maintaining LDAP databases. Many LDAP servers provide GUI interfaces and updating tools. The specifics of these tools are outside the scope of DICOM. The LDAP RFC2849 requires at least a minimal data exchange capability. There are also XML based tools for creating and maintaining these files. + + + This mechanism may also be highly effective for preparing a new network installation by means of a single pre-planned network configuration setup rather than individual machine updates. + + + +
+
+
+ LDAP Security Considerations (Informative) + +
+ Threat Assessment + + The threat and value for the LDAP based configuration mechanisms fall into categories: + + +AE-uniqueness mechanism + + +Finding (and updating) Network AE descriptions + + +Finding (and updating) device descriptions + + + These each pose different vulnerabilities to attack. These are: + + +Active Attacks + + +The AE-title uniqueness mechanism could be attacked by creating vast numbers of spurious AE-titles. This could be a Denial of Service (DoS) attack on the LDAP server. It has a low probability of interfering with DICOM operations. + + +The Network AE information could be maliciously updated. This would interfere with DICOM operations by interfering with finding the proper server. It could direct connections to malicious nodes, although the use of TLS authentication for DICOM connections would detect such misdirection. When TLS authentication is in place this becomes a DoS attack. + + +The device descriptions could be maliciously modified. This would interfere with proper device operation. + + + + + Passive Attacks + + +There is no apparent value to an attacker in obtaining the current list of AE-titles. This does not indicate where these AE-titles are deployed or on what equipment. + + +The Network AE information and device descriptions might be of value in determining the location of vulnerable systems. If it is known that a particular model of equipment from a particular vendor is vulnerable to a specific attack, then the Network AE Information can be used to find that equipment. + + + + +
+
+ Available LDAP Security Mechanisms + + The security mechanisms for LDAP are highly variable in actual implementations. They are a mixture of administrative restrictions and protocol implementations. The widely available options for security methods are: + + +Anonymous access, where there is no restriction on performing this function over the network. + + +Basic, where there is a username and password exchange prior to granting access to this function. The exchange is vulnerable to snooping, spoofing, and man in the middle attacks. + + +TLS, where there is an SSL/TLS exchange during connection establishment. + + +Manual, where no network access is permitted and the function must be performed manually at the server, or semi-automatically at the server. The semi-automatic means permit the use of independently exchanged files (e.g., via floppy) together with manual commands at the server. + + + The categories of functions that may be independently controlled are: + + +Read related, to read, query, or otherwise obtain a portion of the LDAP directory tree + + +Update related, to modify previously existing objects in the directory tree + + +Create, to create new objects in the directory tree. + + + Finally, these rules may be applied differently to different subtrees within the overall LDAP structure. The specific details of Access Control Lists (ACLs), functional controls, etc. vary somewhat between different LDAP implementations. +
+
+ Recommendations (Informative) + + The LDAP server should be able to specify different restrictions for the AE-Title list and for the remainder of the configuration information. To facilitate interoperability, defines several patterns for access control. They correspond to different assessments of risk for a network environment. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
LDAP Security Patterns
+ + + TLS + + + + TLS-Manual + + + + Basic + + + + Basic-Manual + + + + Anonymous + + + + Anonymous-Manual + +
+ + Read AE-title + + + Anonymous, TLS + + Anonymous, TLS + + Anonymous, Basic + + Anonymous, Basic + + Anonymous + + Anonymous +
+ + Create AE-Title + + + TLS + + Manual + + Basic + + Manual + + Anonymous + + Manual +
+ + Read Config + + + TLS + + TLS + + Basic + + Basic + + Anonymous + + Anonymous +
+ + Update Config + + + TLS + + Manual + + Basic + + Manual + + Anonymous + + Manual +
+ + Create Config + + + TLS + + Manual + + Basic + + Manual + + Anonymous + + Manual +
+ + +TLS + +This pattern provides SSL/TLS authentication and encryption between client and server. It requires additional setup during installation because the TLS certificate information needs to be installed onto the client machines and server. Once the certificates are installed the clients may then perform full updating operations. + + + +TLS-Manual + +This pattern provides SSL/TLS controls for read access to information and require manual intervention to perform update and creation functions. + + + +Basic + +This pattern utilizes the LDAP basic security to gain access to the LDAP database. It requires the installation of a password during client setup. It does not provide encryption protection. Once the password is installed, the client can then perform updates. + + + +Basic-Manual + +This pattern utilizes basic security protection for read access to the configuration information and requires manual intervention to perform update and creation functions. + + + +Anonymous + +This pattern permits full read/update access to all machines on the network. + + + +Anonymous-Manual + +This pattern permits full read access to all machines on the network, but requires manual intervention to perform update and creation. + + + + A client or server implementation may be capable of being configured to support multiple patterns. This should be documented in the conformance claim. The specific configuration in use at a specific site can then be determined at installation time. +
+
+
+ Implementation Considerations (Informative) + + The LDAP database can be used as a documentation tool. Documenting the configuration for both managed and legacy machines makes upgrading easier and reduces the error rate for manually configured legacy equipment. + There are various possible implementation strategies for clients performing lookups within the LDAP database. For example, before initiating a DICOM association to a specific AE, a client implementation could either: + + +Query the LDAP database to obtain hostname and port for the specific AE Title immediately prior to initiating a DICOM association. + + +Maintain a local cache of AE Title, hostname and port information and only query the LDAP database if the specific AE Title is not found in the local cache. + + + The advantages of maintaining a local cache include performance (by avoiding frequent lookups) and reliability (should the LDAP server be temporarily unavailable). The disadvantage of a cache is that it can become outdated over time. Client implementations should provide appropriate mechanisms to purge locally cached information. + Client caches may cause confusion during updates. Manual steps may be needed to trigger immediate updates. LDAP database replication also may introduce delays and inconsistencies. Database replication may also require manual intervention to force updates to occur immediately. + One strategy to reduce client cache problems is to re-acquire new DNS and LDAP information after any network association information. Often the first symptom of stale cache information is association failures due to the use of obsolete configuration information. + Some LDAP servers do not support a "modify DN" operation. For example, in the case of renaming a device on such a server, a tree copy operation may be needed to create a new object tree using the new name, followed by removal of the old object tree. After such a rename the device may need to search using other attributes when finding its own configuration information, e.g., the device serial number. +
+
+ Conformance + + The Conformance Statement for an LDAP Client or LDAP Server implementation shall specify the security pattern(s) that it supports. +
+
+
+ DNS Service Discovery + +
+ Scope + + Service discovery mechanisms provide a means for devices to announce their presence and seek information about the existence of other services on the network. Many of these mechanisms are DNS-based. + The exact use of such protocols as DNS Service Discovery (DNS-SD), Multi-cast DNS (mDNS) and DNS Dynamic Updates is defined in RFC's referenced by DICOM. This section standardizes the name to be used in DNS SRV records for such purposes, and the DNS TXT records that encode accompanying parameters. + Security issues associated with self discovery are out of scope. See for the informative discussion on DNS Security issues. +
+
+ Use Case Roles + + +
+ Find DICOM Service + + + + + + +
+
+ + +DNS Server + +Provides list of DICOM Association Acceptors + + + +DNS Client + +Requests list of DICOM Association Acceptors + + + +
+
+ Referenced Standards + + RFC2136 DNS Dynamic Updates + + RFC2181 Clarifications to the DNS Specification + + RFC2219 Use of DNS Aliases for Network Services + + RFC2782 A DNS RR for specifying the location of services (DNS SRV) + + RFC6762 Multicast DNS + + RFC6763 DNS-Based Service Discovery + + DNS Self-Discovery + + + The name to be used in the DNS SRV to advertise DICOM Association Acceptors, regardless of the SOP Class(es) supported, shall be + + +"dicom" for unsecured DICOM communication + + +"dicom-tls" for the Basic TLS Secure Transport Connection Profile + + +"dicom-iscl" for ISCL Transport Connection Profile + + +"dicomweb" for DICOM web services over unsecured http + + +"dicomweb-tls" for DICOM web services over https + + + + These choices are consistent with the names registered with IANA to define the mapping of IP ports to services, which is conventional for this usage. The choice "dicom" is used rather than the "acr-nema" alternative for clarity. There is no implied port choice by the usage in the DNS SRV Service Type, since the port is explicitly conveyed. + + The DNS TXT record may contain the following parameters: + + + AET= + <application entity title>, where the value + <application entity title>is to be used as the Called Application Entity Title when initiating Associations to the device + + + PrimaryDeviceType= + <primary device type>, where the value + <primary device type>is as defined Attributes of Device Object + + + DICOMWebPath= + <service>, where the value + <service> is the + path component of the DICOM Web Service root as defined in + + + + In the absence of a DNS TXT record, or the AET parameter of the DNS TXT record, then the Instance Name preceding the Service Type in the DNS SRV record used for DICOM service discovery shall be the AET. + + Further parameters are not specified, for example to indicate the SOP Classes supported or other information, since the size of DNS records encoded as UDP datagrams is strictly limited, and furthermore, the envisaged multicast usage encourages the exchange of the minimal information necessary. The existing DICOM association negotiation mechanism can be used to explore the SOP Classes offered once the IP address, port number and AET are known. The primary device type is supplied because it is useful to indicate to users the type of device, which is not conveyed during association establishment. + +
+
+ Examples + Example SRV record: + + + _dicomweb-tls._tcp. examplehospital.org 86400 IN SRV 10 60 443 dicomweb.examplehospital.org. + + + Example TXT record: + + + dicomweb.examplehospital.org IN TXT "DICOMWebPath=apps/dicom-rs" + + + The above examples would combine to define a DICOM web service root of: + + + "https://dicomweb.examplehospital.org:443/apps/dicom-rs" + + +
+
+
+
diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/TestData/CT-MONO2-16-ankle b/Source/Anonymizer/DICOMAnonymizer.Tests/TestData/CT-MONO2-16-ankle new file mode 100644 index 0000000000000000000000000000000000000000..ecfe6ce890b01cc7ecf6cb5cb1e0762f42a7ccc7 GIT binary patch literal 525436 zcmeFa&5|U^k)WsXn;DVWTyRODjO@uMyx_u??gnNCvnK_C<}A2701050vXm7WJ1h@8 zaJg6VvU=iqpMauAK88MuJpvzrpQ?wKM@D8=S65YcVNl*C;ch=_KW<^4s+xIZwaBOa z@7KTi)!%Iy!r%X^y#)8IY~*+UUEcX`v*rHxzxikK^_Sm#`HP=__2t)Jef8BZzWmEC zzy9*)e7^qjuPNUl@y#Of_0Rt5>%T#qDnI`knXi8K_1Ay-&Ch=E&6hv>tE~9fl>gP= zeDm|4Z{;V{{HIIJYMW}b`A?sgOl|(vmw&mH|3d!oFOKr{KYa5Kf7!P3U&$}OeUxAQ z@wfl*hd=)A_rLtxZ`(b?-t+l?HX`?g|C0Exzy1Ah{`I%N{i#pzUVpNC`3w2xTH>9b>|UCjv*zFY&CkC4>gQkn?5lhEub<`oH_vjuV!YJ) z`k#OE*Z=&RpWn;ZH|d|{Oar9{pu@P{i@Qx|6}{*Z~x`L`u1M>Px1+`Yy5|2IsfS=k2R9t ze*1^t{NW$|mv8_64?iKD-%gJ3CxoYM{Kcg-bN@oxPx9%$`dPc={@>i2d?8n8u%YxQ1QrEk_~lmEQN%hGL) zwU;CMy8ZMrqW_osPsz*N4Y9rC^ZwoMe*3#${fpoK?(e?+2FagPsce5rODUJJPu>{C)k^H$VTW{7)7C`B#7Qmp_%?|Kv#if13sE zr}BrIyBz5sEB5z)82@pa`D9}J$NV1d<^TI9YglJsoq=@*))`o5V4Z=_aRv;pa1HAW ztTXU=&A_bdKCh=^v>It^YJVr}F-^hdK3B z;%RD@S?^DO({ejY%iJ!p>p5HGGxu6%iKom`XRa~j%&A#ozAtUf5=#qDV^7QHRNhnW zX?xeDbNP}t$w9T`l3LO)#h$nNveeS!hw;C(^E~!lJwLDUa?ae&e1HCoQ@z{r zCB3wJowvl7SV~@}UXCxh8Cz0IKXW_RWlMaD-KOW-Oa7d5d!Hrd`|D%4t@-(+=ee2h z^OH~M+uXTyx-X@RcAsO{WhuR+mo~2R=6bW_b=gv5iQSezPtQ5?eQtSbW68Zu-ViG z31^w{j}7P0w>P-{*PA^ljc;Hs6=>S$e)- z*PWym*}3+$%oM+td`W((`Lu54-^yqDy1iSOm*l7RZ%gLf*QvNInOm6e^GK$aZm+je z^SWPZ%@XtdwuO{@UTdlURDNk?iskjXeo8MgOWmh=Z%a*eW{KPT(&7@EW#*dG)6BW^ z=;rkE@h&aRvAIsBuj|d_>wTJmd2Xipnp4vZf7cvcOQ-CVzP{e(K5cQSe_i*s{?j^h z>bmaK%k?+@yuC?gu01_p)|hkV=U;N>lIcE|Kh4e5^BV7xv$Qj}Jdb9nJ;&zzQs?E^ zB)3Q}v1|Dycb1=X=KHnu6raa7m%J|dTz;0F?{gd1_m|6VbH6Kn-Qqlsyf5{Z*mK#> zGiRQW`99A=rf>7+viZK0PqC-hWnHKITz0*Zmilvf-e<|F)|{UEobT6lm-gq<=k49f zyj1@-Z)t5B*UMvhUh7?QmbMq^DYo=7m(2I2&dag6{#^QUy(RbEA4pG(d>hx2`Y?%SE0^RMqqoh3GZ`%BJkOSdIc zi?_MA>Fa(ozLekAzfCWaOYF7!*Ah>Ae0>E!uk$WBOY4jD6wB+>+bpw4%sJQhrF4!> z_c?VfJH?-tFLE!%p3l^Gz{f)DoNJXPG7aQtZ0Fsm3fd-{*Gb z`}^}h*L=O@rH_m~mwTQw*O=~e4@>TAvo=dSEx)ZlrLSe5O5Bd;yX5Cl&MiK@&uuTQ zK5Z{&B*Ri`uCe6Y#;zsi_|rCSYkYp`X2ns?`aSsQa~s{6FPmr{#-md>f$ z_)F=RT3hqpe+HJhe<}9#yLta!)|x-;3?z5gJ$&EvZQa}YQ~5;qbx%|LdCqlv&*QWF z?L8&uyt&qGdd^?(A7=)Z`F$z&{QLMgy{@f%ycw9OoSFT;>D!Ua^>6cEUJG-t&!u0^ zxt5<-+Fcp&-P`(8`LaS5 z`IjY^@~5$pQspK0f{k))qf>2Bz<5`KG>a`f303ig=ov zsilXx?6#+uWM-;a&zRv$5^ZW^xvL*IhYRy?^ zV4Z0_23Gz*PsGjM(0bsfKcuIJQzUn;I!o?FZN+{+TnoLOQjol}eaoHN&cdVjri>1ByMt$UlA z<>vcs`I`RpGtfmhu^F4T)vaxP8(Z1wuJ3H;yV%Ko?D}RIWs^>5tg-aJ| zOYR(-?~BaaV+*;rJx=vrE5Ge$N-w!f?7IJV;s09xZSj|Tcw3FN?EPi{DmS+AB3ZV{ zawjCuqk=%Q?_=jk*%iOAy!GAK>%k6jkmKMw4ti|Ftb!cdVeHyo_nlySC+3@wA+l*3 zCA{B=|2%3f>@BgUPqr{H#g_C_xz|!De_iJ~o^@U?e=0er=KIrDm)_r<|GA~>`&4^w zZ7x~Tx8-k7&-K>(?F=YZ*u-VxP#}L`xd$O&-Uxjw+sJ%AeIpy&`q7U08GFh%S{A%* zWUsr}JKQ%)Cz(j8gC3-Up*IYVpyL-fnxJRyzU}kV(FlV+1fA2jZPTh%`)sbc!mmGX zd-92fTPEICV=lX{Gxe6-%%x9BUhebOPPS1_~fgBV)pvWq*L0-TsL#F?o=}07w+c#+xBnMQ#+IN?RAn}+P*Dc>drFr{dOELrLS9G z>P>yjWqE(8*Uvf6MW9`F$)D~7-UpOR1tsQ$ATO}>t%5+kmj>{5f4}!7-@S=aDHh}7el{b29^leypWQ%<-A0xI}WxY-coK}1{4*lSHcCd}B z)o*{M@oA-AUtfCC>lMAk-+h(8EV<+^v1{h8b8}QndKts>(Pr7(%9obr_U8L_4^z8K z$x>^HJuQ7+durozzWPStEQ+}f)q+hIuB|M*R(h}#*W%x~8vF5-OPPVypMuN4``7z` z|9%?xdf)EGVQg*FSQoJgthYh0-~lMMe;gctAmCro0KV%*xx>F*==a)T^wI~BHos(o zv>pAf-|1aDIy(cM_8xzx^gms*x9iz1PyIB1NiDHi^0oWRl1uIqyB^VXZniw9=ljdO zz0UtqZ;H+7`M&fpx4e`*?f+^0x!zOhxsA{7u5diqQ3v!GvBut-9IUvPJs;AFt6rpM zu0WYx!jvm$fPns1AvSV{)j#W*&&lqMJ;1eq8n% zabe%e_IE}_%{ple&K(Rb)wgnxdpXg^sT1sexbb==-O>22+ZvSs}!z?+sIp61&pQp3NJhHscElf3T({nq2 z&iBS1yc6O=wHE!O_>ao<^6Ce6KGPu`iKyUc0H3`3(g;>MKsS@5U-);?-MEvp5yq)O zrQ8Wz>ajz)i$Yy5|pfd&aGh7Ijo!Io-KHJ~>`E4J;zd&izjl{Bbx==bsw`i1p@51{1* zbm~Q|KIf{0*u{*h<=>;MekpDXk`!SMWrFrM(QSNuC; zF-Fn$_!Vb=Xpi#P9(rnk2YK`d$LBD#PTUX99y_~&wjKH0Yxt9#ftCLsXJxjT;G!(H zfgN{fACNriiUcdfNw;EumdLacqI`suO1Y_KN=ntU@+vwdeb?`k{@^oCa*Fi+kApqB z4%2rKcK#2_31Cq z8cD^2q76FzYV8R|o;~cx)~MxwR=$8S*Z=s#%Ks1h9R{a$mKGdqtAv0Zt@v`eqx)X%77XMcKlLLc&8)$}+K0D=tPu*zQwwkAVTZK+P+R3p0 zUK$`Nlsu2S?|9_FemtPx4t@-Df$VfE>_?)4T+aSG^c(y)=pWkw_xgtaKnr#H6h`4_ zfF^z4?x6eBu=~LYU_9ufat<&!5g6!$hE{OiKpH{OKyqyRvF#hZnhjgypKJzJ{y*O8 zYcL`3VeG2|b^>}v+SuYhdC6eq1z542skTYEfOaDU>0bN?a+%}g2|2Mz4hOMz=*QT} zwy}E`&hH(MKThq=4#F-Y?g#99X@Gz};jNs(f8%7{ivP~}U&kl!yKK|xU0_`xehN}! zkIVS2__wnEtwvysK`%NWHntsyagxXJ7>`ac_>Vdh7Tr{cqcT5ccow5P0=*Z)JP+!Qor)+I{B~PT?=u|M%MecXt1hCvx|} z2|(~m068kBg0^p4EzL4G*ArUrjM)iR!Ak=e`B~C=qAXW*kQ4f*UB)PfIO${mLcb6~ z`y%R{fMNd)`UzNmKBT0^7-#_7%T_<}+PAg;XYb_KIr;uG00-OP{M;r#de@5>g_W^& zG(yL-mL2OIsvDmu{yQ3=LwKf!$c6cM!+cOWsx)gU#0fib-}G%945Hh8JHa=M_Xoq~ z4s5+~0w)iSAKuXb&gq++6ictXjT5XdF*JY`|B|}_C>->X?JGn(eRK$K6*`(Ac#1~9 z!c~j^PCSZ#;YnPKGk`e7gTTFF{}nwDR~~?4|E10dj59&!G*I&N@!-3$^7qMSV734D z-rqr|CWOMc}+rd*z zK{kdbFzCnWO^SGh_YCa~|C!^AST@cKAbS}1dRE3@596Uf$`}4P=s(a+f>sLnSG1M! zo?xUd;4HAQ2k=dM@42_N=0}-)70}kDYG=YN9NDBPOK{LKN<_N|bW}O{$Q|PlL0DR7%(s+YWwr=g<#b@ZUIV6FyeH zfSd<=ZsOQ~JvUDO8^6yV=`TQE<19d7FAkQ!2*4}o_x&(7UVQ~xK#TqCD??oN1r7G6 z(9vogT@Z{*sMo1!oIm!HI_uPV7tqi{dT6`Bldj=Y z&cMq5_nZAzUVCf|x*Oy>-hGFC!}`nR1ii82Z)k=LmivNy4{&HlKOnVJY^PP|#k?1F zb6o3OP@y6!-{5S#(E~w#+qSz7%LYxbRW{$b4{`+3JUxB1gPtw9gSCKvsaz+mgCtZ7bB-opK^*oE0cL201J5038oNjvf9-+5dLz+g58(tDpXie_^Zw^8<+Y2tBPY z{5QOPh{A&wY!bd*)DlhYWNT(nc4wAH2`{h`CZeK9x#AYOa_zylcR1u$Crz4YdcB^YJH z8}j|w*qdiuV?X{3to*<0oC_>BcKrhXK^PSj4?ju^w9)~s{Qr)JAZg*ZS)TER77+Y^ zQGatQ*uIVi5OjcSZKDJ$&$<(9gkGdMY~&|{6oIjkgR;AKPWF0Ij0Q0L0D&&geSi7a zBQ*j(wiNr0r{KqNRL=Vqz0e>p@JEz7t@h;PV2cGE&`bBUVqCcDWmK6nBu5sE$dMXc zl}~K?UU>gE9y``wp}#@DZ~`#V@5R4x(x7<%4Nai%Z;Z}41036ac|Ke3pI`=7{@-=o zDSLL-I`71t*c=2qZxv$K%JMro?TwC5QsW{W+lHnvG=LKX4G^gr;J;tl*$#74XcyL| z5lU%@*!Ug`dLD4Dxrb&a-? zL4QX-IXWQH6UnjRhxlRa`i554@QG$%<^SE*UKhpoHFm;^*WL)qdoU{t-|-Bz{D6Ut z4=jBz4KOHe!~$s{G~^qlx_A75!BvqSG9UN%2_ks?2e#3)-ELLO(2w6X8^QcMl} z`+;S*(g9KXfWb~68Iyd=R=&UADg1kR04eL}hA6*5Yj+SQ;Z4EdI*xtYpwa2q%ClGE z`iB2bZjjR_@-M!>?Jaf5Ss(l3!1gO+leGEgPB3By{Q~=g{EQnrfWbM1+{MB6y0uTV z_Sb%2KmVKcLf)r&dOau0XQ|ux(>cDDnk1L@pGr=tx0k+Dy_LpD*?|`5_12IYP(7gAF8mAhTQTO)Ht@QYo*NRK z_H^Ix8aBJ1dfIjU-IqAb7lT@*>(J>K^D-2{u zwb?HDW7B4W1ZrY z{(7nQ50e*Z?=B2m@o!KYmoPR8*Iw*5bizQQ6+4Q=fF6h9ffni15;R2A?m^KQS<6Zb zbS!;LY#~dJ7dnVM)kCp#VHUrm8*^^Ps)XikyNM!|E+so8-S;l9I zpG#iLPPLZ0Q_ZElrR1e{K5kxv(S{B%7@mTa9`Mow0x4NON+UQDvT>}w?d&ip>y#>a z19pVcv1#xuyM_kP@&tmqdf`8cer4XA5JEmRd^@JU=TBRmw6b%Dy$9uA02l}1BtSXY zAG{0jw6$ep9j~CH7Y6)~y|EAQqjCOD4LFDNlW~3z?f1p%%b1{@7M0Y}2|0+gMA><5~lZ(~D#5$~|p zi3vGEXxZvE;NP*b1Fj4W(9r?%py$T_=f|+}J%8KB+rTz?|3}F@=(p@0{snCy{Qf3C z1XvvR;~bCi;OGo3j{9jqZnglav>o^X11;5g|B&PE8{w_L-|0#DyB~W9=ni%yy>}0|O1AXC>YE#2fw_`el5w)o-7D&!3Y2dG?=@{XF+2%CGZYu0OZ)@$Zi9 zll0i3v0ccNiOr%u^OBIZjh%7Q)_Bj}AVaW^%CB$YV30JnNFeXXWqVeq0Ud9P#90grL>eTqVy`C2O?4u;>E&RvtDh9x?)E?Hs4Z`*%C)vF-a#(%;`U zd;r1rH+BJ%nt@zT@1bmd<8HJWsZ%z-Yp~rQp0>_ffBP@6Wl1YM2{yjNdmv-%|K-l> zmp-1>yvmWtU;gl}a4As9K&hb*gI@v$TEWh3l7H~}Wl%p4xDPadR)t#)c&Gfbbvv9M;inm-FSKf zoXsn|HE8Q~&h8Hm>(GUT*V+S&gC^10YZ7-B*p#!O4T4`DHkj`a*deeJJk$I?gY;3) zeow8(c&Ph>qYsSJJb`~r-IZGGi_ZRD`L(|l{a))*@v2P-6`VcVsMc3KPC zb8N7pW7^4RtsNB}v!2Y!d26#Hy^;PFn6!D#I;?i|0r;T<$IM$56^IrI)%5&nv2 zoc!D$^^y`af<82!MIIDu?(7Npkk=WB(6*OXFgW{{H~cG>zSD|b_!ss+{eIjl&pmR6 z@@>Ux|33lucIop^i~o64|5)x#dkU zAyVPT;a2M8&h`TjAoz^Z1&Rh}Tjd1NFZ4U#nr{WmP91pb&>yrnxGH`{I|q9UAvDhT z1tNC3)=VpN`$Pd!beCcg8quFYEsQlWBmJfBT@h z_npGLVd>*y@gx0uY#b{eF60e+1Dn@VAHawfLz;FwxquYqOntvHNYF({AOiarKy8iaSKf@*jU7$50>!>IA$eo`#5pl$jw`IDso zt{pr5_I>Q-KA_dplVD$Aj{{Go-?y_o27CJcXpC4=W1)K*TEJ;F19#*V&hukcvoRV$ z14wC)Y^yW6$Xy3|an%0bivPCq|3PceKA`r>|A)+;mHijU@9b+8%ciGx$`x*S&kfE4 z@&slKrUvB=Qj9Rplt5EpMIAYVlz~m8r97`^572f7{k|P&0NnMEP$cm0&@XV-;H$%r76pN= z9B6@#HS|*>-XYaeu1F$I2v};+g2V|n*`vq7(E@@VP_Co&fb-@~?i)1IMsen4cm2`P z08T4|z7>n^xu3@eP);EQ%_Ouelm&I=ti&4p<;b;84D8^XL>mA1AW6y@i=W2H(FWse z_eL$RFpBl|X!K<~Q$Ghe?Q!5AI46QF{|^9D094}BUdDaQkednZ`wA4f~Y zrhU*Fwzc+q&%jLW^ErIZ_bfA&yk0)beGYfoDd%Q_2cWEJg_#C}f|U!HiDKNj2RuX( zDp<}EF>}UW#D(#QtaNm!58fmW|l<_p^_eWAZjq^0dGiZ4mp1txE zk&L5>25AYTOI8a$06+om>(e|vP~^9ddPt%C42g4tX7e{Xv2gKK^W|FZ_T)dwFw zs((B!jO{pB@iou@dWw@^<%~5hrw%ia00jc?4X%UOlw;WXj`dWek$@^g(vj&E@5uG@ zz(!_pLbl(keM3edzC)VAV78*EHHgW%qdV+Qihag*P%M0oaBwPNSn>ve!uUGlv+@Bt z=Y@l}7;@;jwI&Zqy@WgGjz!Yb>z-4Cq3 zz?b3WW@u#v8(#UI)c0m+^xt82ob1?vazi=>c|H4#c5>Au;he}l+zNm zDRasicjGhYTTbW<&h$FIgW>@QqZ_a$aHl7S`^MNDT{6iA_CMgi@#(xeSex=0X{v zvj5Ps|ATXlevows@QF0Qhx31{4?Yv>-+W&xwrj_dPa3e4?xLOgf#5!?HuORgZL<0|@>?<5vTkz5=HPhBj+>04@2Z zw{fy_hvRtY?DqR*w;!nycn+_z1mhTtRi;rD|Kn_w^~)3Z*?4}b@flht=+pQO;^6(m z*va}EfKNbw+U_abQZVP|eJ-2xru21w=FFwnHRhUs-uJc{yQIcArT03gSN^WYd2ini zaVM~5Y4M=Q3FE9qI7~rg@3~@QG5VLLiuE9Xp5-7#=-X2Uk+&gp6O)8Z$NDJ z?u7B+)t}O16#tzOJEL*~r*r8$JqYW#9S48OBd{O!%l+7P{yI?k^^Wc2&LGNRX`GW1PYxhL;XJRI z*9cm7%DD?^Bi4`>?gC2!U5d6aNb2XoU)v}_pf8d(G({^9ps@##yT~)WJsJR;9{B%( z2I%MjDeCi@LGi~2zPRE6baX|#yelx;H|RC`p28>{v2A{kNsNI?ZM!`UC!9N&i@RemHmIe9|SMz8nHkZWEkj$0gb)R02=QB6>UKpiE)yooSx&N z0hCcSdd^^k##jZ)jZaXPWABu8bW|r-A4f?IMjZrWbdJt(dK`C#)-kls*y;y<54P5S zj~SS~_I38&X9k*d{wBxy%Y5Ola~i+V4}PN`{7S!J_Z^)eyMYE!G=Og!8ep6{v`g`! z4-*qfL@%NPM%sm;k^B&Ea5qV2tp@!;3vm)Gv!-u-;Xkmo`e>9J7B=wf2Rnd~r)%&p zkQVv30OV;B#~GhPpb!)dAn+)h0d!8|W3Pwc7tNg#zwf8Uc%8Ad8a4egUZt0Dwi=tl zw=>$owGkYBAW$zk9!BWYlNxt0^8Nz&%15yR{ zXa&n}5GI_+H9UZU{a5%WKiOo5uQ1*5>DzH&(S?%vr(k?a@X|h=7=4_LF={=9)|i89M-#NvOXW3mG=bGKT8^4er>>lhC+YPD zV6bl*hqmwQ3E&5^{5~sr<^O$VE$ei6_x$&`h?xkszw!5ag4GY$SN4jc3f z*Pw=;;{;>yFX_|s3xI&;2Fab!2nK~w{5O08u|8i`+GikK zL#IcfZ$kqOMyjQY2F&*h|5Gfb^bP+F+iz$&DgF)3V(HItLZR#50DJ(&_TF_@{=e_s zwcO`Ju=1OgKocl_eaG?}^vm9$zj1=z;U9+Z1!N!fuY(TCJ37G259qsw9amT|i0`EV z+PPsF1^zQ9&;eRB$wNPTQgha%8y(Q_*Lpt;eJNr@mk6nlSfe+C6;-~48DtlS22BRl0j*ZHHA){C+$bUG(b5fm?^dM}xtI)gqN zqik@krD;ZbAdoKTsfHdf#?nd?DI@JQ@}P$EWO5#iSKwaBQSMS_k&J9_TYJwHyw>}W z8Cd!MezVs$$*~vxTDHGq{YUXHFhAJ;x3c(B?6->!5VV04jQhX~(4DXo=-b9VKp;bl zSL1Z8VKD;@0Aowteh zHuxXq;Wy~+$C2$21pBXdUc`57f6G95BlPKS0qEV)5FL#nNiSn;5O4I`7@MEv!oQQJ z=pp9`8hR@je&>n*p8c#JigNW*N!2ekJ|0Mcv<=?)?ygSyv?PKHEe$BUkTjNB3 zK(XQ5H|TbJeor5NCvfhBLB5t|XuJ;?><@(Xo^ny1fbGV?;jpJgL=!~mfRT(ri$HOM zXL+zQF{F@4t9ftmME=Q$DKrk~cQ|hFsqi-Lt?c?h3)r1u*8~1L_CK)yK_HKFz04Wq z5lEiHF~S_%*vCPke{i+>{|E%WL}&NVX^*z)yAzCloon73+$&lja}@HexX-KBi1OZw z|3M30yZ?dC3Hpqkt{UKbtVH`rvMc}ZK9dgpx^LTH?uGaLa-m<~-HUiip4ks6>JP@^=!F6GQbFJFuk}>IxHCAd3FDHZq7OS7pl|&nt!i7_ zdB+)8`G427A{$MDdFLOs^BuiwR)C~I$OHMGT z(+hVFd2;CRHu9H2Mb1P@o9O0h_YJP#3=bgAz3i->JJ#M$aW;H#eQ>Dj*xZ2zu=3ND z@^K$f=y!%~4|e5B%OoihY6YfsOBY z`t8Dh#$-o+oeBmTz>5EXe?BrVVs@OI{Yh^KZ9cR1y8oD5*1gAyF=q^TW< z88sXYkU2(~;1w97?R>oGRy0g!d_mvzUX%}*4}$X%`nC!ki=U|)>2xk}*Y4V}!GDfO z=plRSduOhE)c`-523+~KcbMw|*W*HeOl+6O-?3K)*;ba{D3RT$a)*A_alIpOw1uR^ zuI*#r;7!xRE)C%P2Co%ENvd&vH=tCpv3lRJt^!>HrUP97KjaRyLnAJbA8FxF{jMKj z?i}#0_yhgy244T4VEQ&I})1%$DhsHU8mZli! zg_H^i4EEW6uth>zoCPA>N7r4e!m_AEz=oERL_mkM2*Dv!ECl|`zWj1)>gh_23G#R zYmKyBztC?RfnqN!-`~*hxn3{)kCHHGACNDlGknuX1%dp0KLCl1Rd28qSd3g!Wxxm0zXlRm-oKmKD0soSO zv-41RH~3e=AZD;V4xO@=3?eZHM+Xf0AFw)d%(T%c9TfykpcgGL&;$cr&|uul7m%|0 zjj{1GmJ}c$hD7BzViR}c~tTJ*bHfS z@&f&Kp+B(Y2Ko6>eEXoRZ3C$R?-`R#)O$iN_Jga`S%79uNOD+ewfe^~*ikoljr91@ z0R80H$$s?o+INz_)1gF9o;DXxLGTp%8L70x)P%NsCEw5idU+d24Y+H_1iB>9ItJx> z3_is=i zJK2x2`~v;58^yo#Zcxzy!OnlwzY8+Z99B7__|G=R)^-h+gw@`$zYU%`6eY=u&Gw7m zZg+CDeWP_h1i!(9@bsT4_mlBHK+XdSl~X?@U#Qiw{f%9KJoGc#q{wS`qpYpaBYhn- z_t=Mk`amBDJbI2{8cU7?)(6xF)O%?HCuNYXC9Oznc>%5Hx8h&v%d0b^vl#=Myql+(0_xTIvU9s3o?v$tfznZXoD=YtRo1c+Z2zsR` zk2m~Vc>uoi?I@ZZ-(VouD=7Ygi|jhrAPh7>r&No6Ef1lW7obIA#~KFqcpN8+IxIPU zGxxiG?ASx6zg`y8^Is7^!5X~tfl#>@_6DAw|Jl(SsX3DTz%B=b4G4Frk6la44}q<6 z1~Eq$80jdTU@$Fcl#V76D2^gmp|=&M3hSLXQb$cojdw7pp~(50U4T%}E9j+OAQ!1K zc-pwIAB@ebHM#IF=!lM7Lr(W2_g0(s*0pPE=?Bli%KzI|5vy?P^;^H#3Kl7Yu-~6T zXxtw=Ji1|Ly& zbCgYNsRIsv^t-_N_j_TluV*9Hpudso&>vXcR_tdBigswn2JM0lX}Pc7Pd(Y8({rob zfPY5|?Q%1i|$ZKZ=gPD*i9F-Qc z-Kb#54s^&MKVyOXU@knzigvXRUf*jAZ=Zpc|JUb7lXkdnY2ESIb=UCtGvs}c>u{}Q z+l@WGU4m|W+YZV~*PRsqfe+A-=xBx^f(B5uN^Aw{6#AutLsL&%2@lTQNGUu-@iVZ9 z{UDqa3X}_0zMZUm?erD~-nW$H4agdh*zUaSbU^yR?o*$>q%=Z6n5Q*{vreob5p|bi z9PH@T!rVWlYUL`D9o^)NPiowLVctkJf*hUlMyNZ_GvM54RnSpJ=okKlSVyl! zJ#%Bn5{y@Oy1oVYfi>XDzr8xcqv%(Bf7=FjzY`SpGYI_a8~(NQz;taJ*~G3NEq}vv zq$XzoMsUbC&IBC(2l2k?+rDdXHy|&=*msDEvZ(`dGCue?p(nBQE?DR&@7&=>VBCtj zsB0rHirRM9kB(*;v>$uJ^KVcdux;c!Om=z<=+Bar8F<~TN{k*HGOb7zes$0ZMxK?G zck+XHgVzq_ov{WZ72vn!$^+**pLlEFApKA)hVU4A=v#!N%kQ;PewyW|p(M0i~=$&iG>6-q7EmKj1&g;`a;vvI%K0J@DS(u5q{W8wTyr%6@EP<^Knr zft7!IWquV>`xXBV@qr$gf`78l(`s)HDXiyKM729V=+?^v za6AP=qX;{Sp!V1qq;f?Y^vfHh{m6R16IQN6kh7w_Xc2yyHz3D&r|^MTU_??{_$aGzg?cki|vW*TmHS69>>A+55mlT6#JH= zyu_Tl)1sCB3Z9_RYbT82-*Xmo544MQ2k^m^+p8_C{J%Um0_O_-4t)y$me!e&xq;R1 z*!{+iKM2k*ePa`QDZ=&Gu)1C=Iv@cH$nSOKJlF}K0r=fc-1lQg19XTN$ag}&^``w z48|8|ji}&g0;G-(i4K=NV`b&NxE6?w`plMn=06xgC#2kazc zEXR(lQqqvhJ1NHIXtBXl`cAg`>gu(%Q0gN&9bEV~X zeF8Lu(%K-deC*=I2NDBH$Hk(f)5^Oar4PKA*6bgG2lP{uYtV8dN8W@?rSue{G(f-T z1FL(#3-nII6Y0f&>~z~daE;qq|J4~-`G0A3IM&#qGqApn2hi~V6jle8x?k+RNfSY) z6D&#HkKs#5k}yQ$BX~A)0O`pO#iL?+q1bHo4)I5dAAM1{9=>6{PY2fdhz{z+xYjj zElLsqcUgLW0NdwCPKvE&>61^P4J*%J(cmS@n= zJ}EJ_4*y&>Z28D{0q%OcBJJbcdHF#6# z7pMzlJ0nzrm-p}7oo5h6p0yhMYxw{{ejt}^2yM^`Eq0#NE_6%P(&FEahP+kFy~?9f zv`1*Zg?3y0k|7|}lk_M2_v&@Ps}=uFdDe~c4BB0khEblsAC;%3HhBX1 zUfQ4|ok96zX=D#Pg`$so@jteG<^LnmZqM3T`F}nq1IiQ%I~u^?--~^P%2s5`#^GPt z1IVuB-{a-K$B}=P%irDxtQeocdKOlB>VtswR%x$uI;-~topXY*m)$5^-(Xv|if5tp zF=R+D{s&j1-$t%)G&K^TWBG+J@D*&^4n{wM)(IFg2x?`I9eb{DWRRq=r2E)=+Wx&A z1j&(1@^KiQ;>w6S!O0iy2J1<`R*2$L-|(Nh0STTipx+OTb{g$!)pT@#Q?B$A^r3tP ztq$&_$X{eTsVOIri_%?Q{EvLbmH%hl+DFP+`F}c-DzRxh-P%SV(%6k&w zQSFDc%IQUa=4xrA_J;jd{C7qg+m0WS-bcWxL!VSISZc=sO=F)S&;%KLKMoD42Io;k z3Zt^r8OH(LeiziTW4}BPnksiLd8I_jZFK@L>M8qOhuBUKyoYRe3RV{0O9Qlg@;B56 zvBn6UdsGc26~yV2o@uE(myX7g{n+|PTI;s9^K1rI{-3UjF2NoYWnhu8c|W`WxgcdY}{Q+m5DcN97G;tAGDvKq{kv&0b7Nat2dQ z82gbYh_EdX>ChI8O9+b8Am6BGSw_5}%beDKnh3kNJV+<*k?P`T~X)DlDRvv)t;@PUTHRmJEz{>yaS{mE7Z`;P{ zTHo1rz+I&0UDjA3aTNc~{CD^t8#@k8{gSv*>l^CP2FB`l-uxRnV36Z?ePRusoLzuZ z$FuA>_73+hLfcXN8$D#o(g)cz{1C~#0lr9Ff-p9}jIc8{*$=dk^Dlx7_92Z^h~&00 ziUD=XHF76#Fm?lDujCELQ@jIXRB`Ockehjox)JvSaoWgMvK@za;VuL#`djgD(A#LO zQ(KPXm}wzRdoUfeXhpdbCx>==_C=r_O5@nbX?OmHe=qAFcq?A~_nmF*Bdzweo!c2$ z`F}bi9s6&4+ji)Zonrf)kXM7V0ht3f1759I?VH%QTEVPgxahgQA>N9XU|&APofRMO zA?&6z-Z>YL;xvx3_pQ)&tM-FF8uVu`Lwf1}yby){juyz6mv$Oh^1ka^g^oZADgO$P z^REE4bD#sX+7>LfL%Y(q!@nfg2Z24(1VsZhM%78@JxeUUZa}_PXw{dn2k3{`#isB2LH5o_=r4VEt0F%%>jcEe?bHyai>t ztk3=X#1PYZP52bDpRYC^Xg;Zu)AXgJrk=q+J#!Rna;Mx;6x%+gWb%qylln~ilmC2r z|9pKe2s{js@iODL0?5IqXz)yazi(d=A zf>9m#(t>o@fo~foAI|*1Ru_^uJs!zQ;rLd6lUwr+ua80pT8yhX3ZPu8Cl-OoA;pKF zqbCZ0{oKg7E=@cTL<(%nzkW1-2~hVh0nffr0L6RL9yW~#z0jJt3Dg<63jcOfw87y{ zAUAJKSrfw)wCd;-HI>$LUX)XV-jeglnO-Kh=w+@EY(nlFr$jgtQrtahrzfG8UYmN# zHG@*)UWB{T<-ei7nE&gw;J<$0c#l_Kzj*>L{(n0nOa#K}r00h6ghhqMcq)Owyi1(o zYO`(!jkPKu)Dv{W7{j_FId3|IK!)CPfBQ$~eIFzQf5A5&#;RHpA^5#7!WHT)M_vML zF|Ih)V&^;(YV4+$xXN87Lg4Q7YWRM?7%_@*2gd#NQE&3@@Sm&#DBJ8O5(r`dhrAqC zSpl%458<4RJ9YY(yWZr8t8-S&Q2LhWlA-`by(TZ%=B$_0n9qa-rAq6jl-|}RS2y)p zH&-#7xL#K|rmuLW3Khtw-xq4c(l4V#N3X`oHTjJGn-LqYvmf{bUi|-dJed>`cHzNB&V9C<^A51?NnR~O#k!z8 zdcO2rUFzd8R6eE9c&Fk|+S zyW)^TCNYE_e&q1V-7A_P_a4zSK)zfW=$3n&+hP~X&Uj`5~= z8C4DDHb|H1UDnnmnbK z`Fuk3QcHepKYc5D9ZNQc?m%m5iPnueY>AhpKKZ=%G<}kv`25`Z&C#xn&c%!WpQ?T@ z{^K{#SK+_Lufn3ke`2#YXl&R|YO_Ik-twM|<+#es!bda2PJRu@Xb3l=e#6V!uE5FN z;2*Mx`=`8ac_?}ykGKcCG}I_Dwd3dIR#zd?_%z|#jujdZd(3lO3N{oC=+y*0#kp_s zXl4NRkh{p9ZwuV5N^}qQqUsL0-`4$g~QCzp4^cV9D<1P#zFXQKlFetwW zZ^m-2jqW&ir{`S$^CR&33IFm@n2~PYH0Y8W+)E$IbrrT1Ytn4Xx9}$8LuT6eFz5TB#TmES^z3eA(uF>=*@ZYNdbGL=QC{O8E)76K5((hjiFjsOEBR0md zbG+#PiBI6g|NlF_N)*pyu%(b?OI|OH$=aXc{q@o?)Q3?kE_yA_Y`tk6f-mg#i~-w? zzjp{C4>Ihgy!8tdL1y(2zTy_tty@EXUzNWTP?Yvzc=dpGW9#wl=U)DuGfGwg3f-|w zQsSc)r?~keSpmpp9_rugqfrAz0VqKMxDxCJcOGyoSVO-Lzg$0-%;2*WmXv(faN>Ah zI*l2E?dLRF_H9~zn%o`wg;q76h0m(Ak~O7YITZdI1t7I1eBN4BnZC#99qRRYEa>;M zynO#pWIyQp#s55oe>BCH*79#K=wJLNkci(+#bf30nYG~3Ke<} zY6a{YJmKwG`L9ukO&N8J_X3n`+$}H~ z%h8Tpqs_Js?ziR%U{lsuPI~EQKYByDHx}sCfc}c|{Yk$s{jziZ5jj5Sj|tSV9iL?_(H2?# zc-#2D(So7Bi3A+}M*({8$>Bc^;qLr-e+t1(43LKoz0}Il26!&WcA|HK*c$J=JU35( zl5HK!f1n_;{3k7xIfLWp?J%CrlsF1dt03|8`g8QB=hyZ80{!*%?CmFv_IUOA|2%;g z|KAxC*C6`ub3wkYcm1`F;(6mN=ejCmTwpzhevS>>$$DPHR6S$*Sf&H7YxqhZ{)afe z`HMk&ZC%JD0tosG_O%vJAD14rIf{06&!yk-{{sE?9y!Y~Hkgj_NN&gD(3a5GFT3oT&EBQ6o#&_G%tPMDOr`E6aidsRvqFwSV zVvmZF1a*B{kTd5>9|`TYq$|fsk16WX%YCx1<9a zH+gq{J1}5MZ+WrhMxnW`c1q$7IqWVML5G}Jq~PDqiT5|Io%sKxmYn-WKRer(k6QZm zO9_VmeBMcLZRdWu)*-Md++}jN4#mk`=$<#qRhyffQ-^vw_fCu1l6uLf{cLHpOw^=q zj<2T=`VKzFTtRQWpGe@p{y_ok$$ek?$$l>VJx}x!Cw=k%r#yid|M7pGvz`M5kc+VX zRB-RB0CDOe5q<;~Vq)mg2*~SY$CikR>H8L}{lgsJ`*FU1H-0y#&O;xyR0$f8{M`^I zM_|*JJ^Cc>Fy_8-v&ka@u~VNC-``@)MGE?b7^Ke*Cp`+l93kP~@3HHBO6+gE`!}?} zQ2=2EP<-FGW@YhBA4W>|dOeInYu_BbQZylkU_QlIVSy>1RNy)B{0iL;>4gbxVpT&) z6>XEv;N6$`phd04@^3@{g7s4a?91=hlY7}Xp!xKD^8fQQiC4}~c>*u~|L@2$QAVd- z?5xXw?vu|m-5LKu6yT)n^EZ>Rks=@H&UQC;tlTO(7Rm}B`D}u_}6p6e`9fp<-bn`|NSA~^dtD*Zx9RA zt^^A6|A7Bq{%yW_i~OsFnH}X!&Ar2zsPs)scw_&%dManbqG1WmT173@=1!E z#B57#6{ApqT=xAR_WE1}FrKhN{Me*y=L5pbtIqXPMtZz@{mv73@&CPXQ72<0I(|3t z|H(XD&jkhgX3$mZ({-6o{95r{AA8I7eF4=D4UGpkVu9RtUg<$v0{gWu6Mkk)U z^?KicAyU)kuGcth*t#nBp;!Lly`<(qx+FW++gfAL!YzHx@Y zeqhQ!vf|8;!--lKAlcJ7-I|I0Tg+P7Kp!~YjE0AW5* z+;MQf!EsW)bAaSN^;Yn#T$%gBYxfb$vn6VX514*GP3gThnz57qlyM%fUcdVUUi^Q5WHfVZBmSS##J@6< z@yh)c@YBTm%}k(~;R}o!{@i9=4Wkb9HXO%#cc-QiZ40E+4n^R6X6U43O3(zy+a@i1 zxVZ(bh5BPjPM@1Q2v#AIH2|3v2TWNd^GmV%-=e3w_FdoeL%$Wo`+oZ`pZv%2pWLmG z%H>}lwO8g!s{qDzd#<%wP;(cYCG6Jv=D0Sz3g)ch`5`cF)KK=ZpEWoJ=YmZ?3F$>E zg?V2MNb+b&ZqZjR{YsC`JjPea>=gV(rFhw&_yk`3e}B9cgcH;HnB@{ z&p~uiaM_1F>D9URDp5jhuje;dEm-zt)nJv zW#5S?ni-5&5@CPvW=7JR9@>IQhA+IfUF~WBhyz*pVR$%IYof z)waIWIOaz63c(FSxfBJp>X>j=SfBv5ma79o53y!={VCHY{e@}>XO0E=z3c}{BFyy_ zg*D=rg8g{eU-ATA{Qu6#8_H&_4fX_1Z07E_Is{&2`FCi_{aYF2P@3@0epxS6A&KO5 zqk$>ai2+y*&}si}tiS~|6(8Sk{_xQe6r%ze>-(?B~np|0UGN*HvHq|NU9~W6I_o8&S8O zO5@#@7KBG8W^&&l;! zJEWh4ej`etHgof2m4IBxHPnDMD1+YnQp`pA?C>afe}m2|%YPEb6!*qeZ-%drWz&AceGp~%lHV5@_Mutv+{KxZ`aCANjS|RFFhA+B z+DLzLK6CTQevgh^`jlT|FkY#@>+cP3Z^Qt!#oxwy_<;EsH9!H9IYH8UQl6X%b+Na4UZq$aNY(+I`|-2ErVl}Q|3I6l z|6$+cTM+}G1IxcI|3kZRC;bR|l8;(FP!vE`2jZdJnR1_g)5A92546SE4AvX&lgPbb zT~-15s_W%_N?9pz&L*eP2)$0rX&VsU7ok5{O=<2p*gkU=MM$Fmdh!4NgD3FfKfe1@ zsIwBI!rQL8$E6Q}QpMymDjzRPY;KRd`C-HBM*jMi>s4*z`x@1lc;IU-h~8 zFjS(^fu7$CA|?>hx~l_%msV7U8rNW*%!vNEE6gqq{Vhs7!X5D5uVKG1Tgq!`H||j3 zU*W#V5z3q#B|rA=^&u!gv%a9WhSh*k!MDd@tO^L^JCx_peK@{6;J&7u^IY&;zbc0%l{=6tEH2FQbs}{-U^QQ%ALqCM)QLs-9 zHfKMreEC=UEIx&PXH3Q`?@xOIFaEzj+Fbl>7d`u~0|pAf=w$@wSuegM8w}T{Prnr^ z5f^8S*VW+FXZ*u*tY=h|z#uvB_cE(~tN@ULJ=-1v{$*NmG)hqfw1M3HEh0f5;FG@> ztk9@OUhR_1O7av}?qR?4tI!=p0riak4bSguI&uEeUqk`T=8!LJy0aHq{(ZCvbyGMs zK0{xfdvDHzYu2(p`(QI{at)HFra^dOvb;A>$!P00<-Ev`&Fo$lY#vbv&M<0luEO?s3z@h>eqRFZg)5=dFqN zi*(S!Zgo%ihvJF0NeT2jbBA2=r@qqPnDg5KjX!p_Bs$=X`qP`;3mWaJ6P29$l-K5W ze)L}IsNd4 zAgE6np5L}y?tKfsYmhU)*pQ3d!EJs8fAVC_%GhP<-`L-+{L)>YYo-+ zB4y0ho3()EyoY7zHu!efpPalUqn=g=CDaXw7iec&pG*1rcHg6n9=z4QLFjwYSKOMl ze?7(7uXgA)&T;B9-uj`-wR|)lJ@N3qTJXugku#LbY?Q=@5&?`Qo1hm4|H9@(05yuk z-Pm2YnlAq`pY1RRC3VqjUX^>XTj9U`TGyafK=72wXat-)TM_;Dq$sUb1YfrWUv!FbBD6xN^ zoK-)E;^8>(_dZemdVeD?pkBzcB|MoIG^W%Y_Jg{@j3J-?KUj5CVt^*zaCE65J)pBM z3cW!(Nz2vIujDT0E&^o;_A>mU6MdDVwpGz452KcI6wH@;rMxLKTF)jtBggET#53`` zqw`hrv!B3=|KESYE(ZUC|1Ui7m|@CFfM4qt_%6RW{Pe8O;n@S;ooIbVwm?gczlYwq zBs6U8DTizF;63hoPnc@z2I`=cCDz!~>Qx46qZbWTiudpmqelmL)31E;B&V;ok$~N3M{T+JAx~5I?`u)hKd*D{7yp0R6L|3-zx(W6 z6yF`^I14KPzVzEENCh!?o`X5K6E_#MD$FWr;mmF}47@3kb)o<;a8_c6Mm6*<qdqKG_`@^?WX3M`km7!3E z$&Y6}pLYdMg{ViO0KM_QavuWMTm_na`P73vsCP6f;omWvyIuZcYC*GNNjqb6JblBz z(S}xsY>VPtsM%Lp{-^esUi}pT{O$;hpI-9f|M#Dt#bD_rOLbtg59e;y5W_~hTAUq2?FZPSWYOfO^9fq!8QQ28c%D|SP^#wYE)15xi@q`N7G8a&_Adhcf+-Xiije0#3}OvtXA{Uxyl6u#XLxsi zuZgSChPvswSUt4qt4eu}9Nx=Nr zXNnRCrY8Q}i52RSc>IF$+H>+y16zwj_$7w2A>Nn`IOj4H-9`Q?>l( z*@(~eUaaZ$C60h@gKryS?ykFr7I-|xcRO4Os=$8!dcd80)PxdN1)Mk`CTf6t<7lMp zn6nsv$m)Wi8F{vAK#acW`uc`%!-BJBCXnzf>*00jw~09BdRr$RN{z9AdBuJ`7VZ40 zCWim7Rij+xs*B>uebVdgZBu$na&>0`#-|f(_Pw(JeeUhLbq+xZ0tG4dvF)h&CviT8w9^qW^ z_|7L9^Zm4jE==mvYFb?5NI36nUHh;t`K)F15LlszO&v!UZ1imF#wvipzrFbXOXz?7 z|KIrC=dQxoJ?BRLEy9|=LZQQp-wjq3VjXLa!EwhuUVYAW^NoU7k*qJQG|x(c7bhgd$FK(X-olJuN4j8ED>4PUN! z^oC}oMN!wWppH{p_E}A|@>oB8uaN}6ZgW%DVbH%sU!NxZOf*h#4=qV*ok>VRZ{$|@x@K?Kq zbwH(-QkN^d%cJfIHFjy?t1b5?4ex0_0>C^ic+Vg3n5e5BY zjsiS$X;HK9N z@~Cwb(ZA6?rpDD+8>~BJlQ-X-d6W4IUa;KoXm0@zHm#sguY&3r^g5PH@ANGXC9dyB z;qim(HI~p#ElT@MC^N_obRfx3>i~ExGTD_k*4<%1Ii`56E|HAu!nqK?G|6inkFaF~Xo_w(iuWNEjk&|D7DBF3< zFO2PAJS$PU;3@S3dLBXS+DI`aWkpl^28Gb8X}m|%q^y-kZ+{*|j0 zW{)D8N$yT>2Aei_PdcI0uG}R=f94bfPl>J)#9V<-Kczx#BVM33^`{r1bjo$NLIIMR zpKYhEVnX~MYmWaHo<_XvFL(kk{{Lv)*|~|)$Mhs8I~ObtO~N;J1>*hg_;Dv%_Z|E7 zIre$CU%Q^4qY*b+-Qt+O;*@BJ>^BM({)H8Qrqr%^qtEf&*P>QkbJiN(gmngauKqc3 zd_%Cj_p+-S3&?Wcs|o@C`4Ij_sm^)?$|K8npel|Y)Xon$k~S28jVL9!s>85w@7yP5 zcyz-*3uG3A2j_LLCne~FA4VPJGsU6v9B3QV&1+wknznsR4Mhh8oyeo|LtqO`2VAyzl$*kNQew(`g-=G`4+xE8deKhdgkW0y5w2ThSkLx z{l5O+@wowAd6&B%Mf>JA2L$$w)x_8>L{EA#VuHBpEv~-NfiNc!=**k)KA!%d)v?@} zyb-$_3gD36b1x6!w|e9whzp9mco^}#O`Nj+zG2_S%hL;eZhG!5$uH_DpG2WrkK)dS zSb*c|fx|TUpK5UAt@*@8O@U?IHcsi32d(=mL;;jSf6{|N`=qD5{(*mLPh&!~Oo`u94L7$8|G2qOD9e8;}CMqg}p z4}8DDe_?hiDg3@-vgs1aF3MVCQ72KF=KFpI<#iMK3cR>2{{{bs_9QcU+wb%wwfbT3 zUy!dTfD<1Cxk;;h=)?o)f#LC;wly)utW4}apkJ_nRE1C{W|*9Y-zEZR_{Z?~dL(yJ zC_teMl)$}_a#9B2w1w(|=Dy0bW;4pkzj03MB0W||tX32?3k%u^pIu)5|DTM*_$lQt z{{Pu?^>YyeINn{)d3I*j21Rf`TtdIyllVPQh=5-?Gn$VWKh6xkVaqN_zF{#60`C?XuqNny6IhpjCsq$UzUdY~h|6*UH?Pz-Zv_Jub`M+QjldZ4;t(`>5ggDxAyaJK7-VNH70J3%S)s0RH4Z zVL#}wQ((FeceQP34n{)cXbbA|q2Q-vzz zQx)b;gwk5hxu+$)R{@-}QG&@!v@0RKIqJROGl%m-$Qh+DbjR*~Pgu2Q1d!LM5%~XH z3-~u$`W}sa!nP7E6g8E0zFX5KRtu)}uc@T8sad=J7br}^zrOhYOZ4x>fBXy2y@&zg z(w7;t1(%KKaP;PnU=Gl?$t$9O8nWTn*y0+KckUGc}n(7_A(e zyqa?7ytjl7>u7%=f(fO_FPlsk~?M<7Lt+O(5cXApWnD4hcSNW#m;rqtuDe` zz&LAsey;+w#K~YkusSJbXoZ}EeNsnP4U+si*)^s=e&cZHACvX|MauE<$~vGt6=2lC zNsksx=_o9JC@dT1 zD8QIb?7&m_w4|h$d)ZpbJ~2Zl`p|75w|Mg3`y`Wo74PpCyW~lK%J_~~uYdRiUi|;- zBc#_)=T+ds7}0&}&;QW%z<1t@(Jr^X#MRzl@NS1)`Xr}>>%gP;M*B&MvCa&@pO%#N zb1qO54Un&wJnB}w1q{4FSy`H!oOw!nW}>TDn+FM@}^JjaZA3h z*E8>Dtf>Bg*iVFVwH|9Q*5OzORlzWBK>weJWGnqc-`%mIYA1HF_No}O)@ero!q z$W`bUynexdvgyTOP4WJC@1?%*_>I3;bVgxwEZ5>mnJr_=-XM+EY;h!8qf@>9-l0>r zMl7?jia~6Eq7>FxuK(A2L#?jWMgVdR?x_yj>-qEAPH#?5Z0+V+)PA=6%%2h?NB$rF zbNahB^v9(zBUXw(9I3C;pYa4<{QrATH6>UHG*+zGMgvfQ#zz|SWP|2}UWb7h*?Ba| z;8$VJpKN^!D37Vbxt)_#4fkk*Y>CYt@1IxO-}))&pFV_WpQz%V&?Jx~yH9BAL*YxF z%;}Og6!zNGjA!|@3%G5pZUOs2&pLAh`2W=s{*_p!X{Xz7AI@hJXjQJ2Ul!bp-afC> zT&Is*6LJj;%rV=3in3TU-YO>aJALMQ|6cy%#s6QRe=q*y-+b!EyE%vd+;{zbts~&R zVVMzp?rXq;-1JgnXNQ=A^f(t5v=DS4_I%EiGC9Yx?`VOX?PSa#?54~ioS46$(>In+ z2n%#(ES{|8`di_8_QjemwF-5APX%#*L9Re|;p2m2uJ=z;f#q6L8c+Un$u)WlB6fk* zyyjt>8&9s4dm2-6Kh3|mPp)UryD086xogK1{$plPPyYKN!~u567ytj}NQ@s)|Hc2m z{}lby7w0=`3b`@_6TQbN&xRKGJz=7*3g^b0wwCzhm{9NR^`gxCW67t7XTA^(1myQg zug?6T*PM92hg%p_;&5R&iUT(SU3tB=iOMekT z^+lOa$u-c>_yObj)yKd01YZ3Ad(Z2$wn1rK1M+KD^F&W|&ch(5;Yn|O@spzlN?gFW zW-sR=rO+;yhJD>lIQl5XxrqS^1?h8Kli&T%o$Wh)1%9*GJ}pW2A@HP2)f^4`+}o49 zIhW0!oM4+epO&ycTcH5D3SjJGQ?c^^zxR`9r;XE790{W`G5aCp)Nd^5X>9t<-3Qk! zHu<_Qy%m}z&|lYB`WroQ*dG)4?-&1nf&RVtkN@DQtH@anMlKORNQx5?#75RNtL}%L zTlC!c^d>fFN%&7@2mN89nJ-=1RYQ5canN2tyv(I?|Ub;3; zx4YbX8UD=4XB=NpyekHj3bkjB@6@eTf+BDF3W)!;-L#zZ*(b-QW_+oJ!hgYN!>b$$ z*+Orh*2{g7m)pYG=hFiEbw7b~%tPrVzv!Rd8bwj8iHZ8f|9>zN{3Lxh?+3J81KL~Y{qoN6NsjJ-`UJY6W zN);YuSO1Im48|J)d2ikdG%4TrfyqiiY))}>z5^h$a=^ER{1N*tftgFx&L@igbQy2H z?fYrU93$6)65DI`QXI>!!EV8>K`GlcQGhYI^~M5*PRw}|myCUue(q&Ulz#Nde>@}q zy6CJY9&@hCzrX$l;D5<-{0Hq{{QoCUZQ97mu5!XM?d*!{)5LH9W5vn z!TI|i!g=z$K*g$|K9oFp?|J{g8ozEIfgU9HXK*geFA|IC|LOUIe4qQr|JV9;Uw(~! z;JbAd!0_Qk9ME|3Ko1&@g}J>Q!5Uwp0D>0O&Qm5WwEpbDwvEwCUzB#+X89N5|2W%! zFn;6J=D&IZFaH125u{@0xcS|0c`1B-ockK10JlCD^cQAuJ?X%=I~ow|$K;$u{*6i~ zGlZrdb$vvKZ*%ioUBga0H|KIKK-_%Kb{p9-cU;HMpyen;#_@L2& zhW~_WqgQ>@Mi$0-&G~9Ta7A0=vv8E z^)TiD#)^Xzy$Ggwe&4f%c*Ek!{!rUQ^$q{Ek%2AKN*yItkZoTvfo-knQEo*KpEZ2u z5nGbvs@Do}dxJ zJfL>2?;LxrU|-MxC*L_18ltnFv9!vnKri(Qw~htuC;M2|C$A5qesBKl9S>h9fJ3xF zxn8GjuTB)Dq$yQO@{9keAMo#d7tsIY(I5Zw*L(5*-;V)3`9uNubvqYq2h7Vkm-6Jz z0Gr6FfvpAv^MRILMG)Tj^;O^U5g+zVzBQ4*z<+&=p$O2NXh6T)Mg|SZ$$Fr{bkQm~ zS9s1xP_x%K693o3p+7kWb?Uj+=cI(TV@XSSYCw|TGmqHcWb~&7G|c)&?F#b^?NgHE z87klqFGLEqnxU%%Q+tRW^QT|$t89&+)s#B4=a(kPMh$vRz7J6U^E}3%`~+V7|A;4^ zqL;W9@9*T2XGaMV`gP*|i{I?1P3uzwCV#G462uP4EIiit2j0KoU*Z4n7E7_bEBS`^ z&+*pK+py|bL#{s*`u*8TsOQu$$WP3uo1+@=u6X+)U6>QdmjX;LQ`@OIwVIW{Wc{z$ z9RKccE6{FVD+ED`GrV}C0ZQ7~7j=@_K5Bsi6q~WOCtI(c`2qiO{MxfF|3OR;FaG~z z`d|P5JAT*`Z#?;!Uy3*&A-?#IfU}~XZ+g>nON_VV*pQ$674SqgjCp(F-}9rs$uIrQ zzXkq3iQ45eg8%S|0U%R04hZstv|<6j0l5v)0_Dz(AeaFZWuz$+=+{RMQ*Bs)E5hb+!k6=KHP>C>mq;fNNa^Xs9i$Z^B%mA^y{hHN!wD1g-eAphaC91t_BX zMjZ_eaBT7gbNbO+a^BQSa_Ve9?57d0p8lmL@bdpZYNWWt*%`&LF+jtAikuz3<=yXn z%0nJ;w`=atrKL^+{qo+-`kh$6zv(afOTvFce&US-;+t|dI2O3}jSePPhz8`to;skA zJT&X@4*LT02D|c%4|?bJvhVhMzTvn0Ppd*Dbgj@4h5pfj+IS&%@BDqUglj_ql&C$3 z3G%4|bIi?A^gvMrCk_y-wtho@KG*jQ1qf{Vabki#nwCwDpa8|HfL-!m8nf}L^~0XP zi~k=tntT<&E`4t10J*DvdG@z{1kts^Z`|da;cDd3dgz%V5}C_+!6f8#sA-6=X!I6poTvcprAi?9iT3H^_?B) zkrM$N(~oUz(eQ6GPdLR78`-a({*@>2;{RWHO8;sdm$(=n&o0i}|75=3(&v2SVVA!9 zyWaDp=bT9RrjPi@Z%NcIzXWlAky7|?{Cd0>B*vqN1MJ;6H*$t?!Q%_LL8{S%L=l8I zpedaeI>YhI9VvbnY^0KTeVC_$`eZ|F_< zANq|pHRbn&b%kTU6}h%KNgYKmgjheH{O2Vvf_?-llJIX=p^ZItrK!~yJL!x6KlKT` z_>Uj@yz`{jBA!iVv&|Y{iJWX!1P&WwnD6$X<2fJ2j6YfNbM|AsO3=?00DCv0aCuj_ z4rT!UK5XZ3j1~x`N;;Sa$Va``czry1a}DS32Kl>wzAb=%gVKcdfOcI4XgF;YAc^>e zT!-)?r_qgkD{`sNTg4@n06w9Sijy7EW_^nmmyTd=FTWdcn|tXwp#9E)46j1x%-l?uq}A` zF}e4X_do;sP}HH8{^>udpX=?7*?qAxsyrE=r!uR37lZ#qC*&4etI^5$C*$>1{$roO zi~k?{JcGCTe#vVexs(Nd%@2EnMTqwG*9pUghxVa*`L8efl{rA{dVqcSdvBC4L<#n- zX071c_Ok2`qe66$?>+sZ0?ypP=(j#-!PS&{{^%uJA-Q3?pxl>RnOI@@ck0E)4n6Y% z6w0VMHf@9+Vk3v)S=j2P%f7I;=RCmf4aqc(O3l)=#lq0Y3Jl+#Kd%GGFh_b2~B>@c-;qX^j^_1>!s4fhVyV|@(&a`=;f zpEsne{3ZMgYkrikPJGsy+&s|df-T)~4C-FeSd!rEn{{sCrIsoS}tyvA|_0C27pY*BdLZJ&Xq_PEk z8rSX3GyJ~36(kER3yRRYdiN{i4(B>b1@lu+9!3w!dGi^5ZNulm`zw4ay~mV7uWk&$ z)i}$wphi43K+%BMh+m2LgE@fh+kTzZzH9K59m?%=LT1vp)TPL;6^;3H00YU!3PEfG+oiW(4trqauQrFDOnt za=j%zCUJjo9miySalvg9bp(pT9^!-AHeQ~ycr>Y4=9OGWNs@D;kDyOw)?g1u0UVW> zs|YQ8G~`~(b=(SWg|onNRUk%jHm^AUEOXSNd1s)!2e9$@@Snr|t_Sp`uSVU3|9Y+q z^hM70Lw_Ri>g^Anz>EJMJwB2Zz!~Fxm7C)|6(V)$!7uyxXd-oi{(^5O0>~x5R{?bS zZ*svR&Tf-S5<1qk}g9wdLZQa zmQ2dn#wLGiB=<=!#MkyE+n!!g1cCZ7`G=Hkq#(Px5Tt`RV$>kl`zxM3;a~CjxiNrn zRzyjTN;do}{KrNB&Kf|T^bbaCyvlyy6L|6eqsNoZxBeL0#yr<IVM=Bzp6nwalqFdk^cn5@#xC!Mp; znv~+(8@A;L*7tZO&lsS2Ld`P@W&pJ-1+fu8jY{y&BwqHXJb@SgKYFAo{3m0ZaXc&k zC;{Iv*4PL55KV_2hoTVAqXYOW^+fVf^s&s=Sg-3w=}Kgj^4w5U$Jw^qNEg!CIhflvKiHP5t*?Pbg+q*)1;L~;dI#wKgV*y$IW7R>~JN4vS z;@hWHqX5RNAV2cGm*5^6uL9JYfAQaMMVW4GUH+$q zQ?szfa?fVpXih&hVEPn3U6J#da;O)qmuhF8V9!{ACjsY)1_X?MDuAMq@r(fKIR~(- zp6!30%pW~2zu(`B|L;GS|2)^l&$e&81ODyE!}&%Y3=tvQ!yrL7Gh4=O-xV}7eur}XQ0geYziHoCP~H%))R3W{b0Mdff1^a+3tukWfn)h5>)Vwa zK^YnaaLh)PAl)|YkT1 z6FnzUZGI_09L;b0$a@bzpQJqkTJsqJ^s0cs{HG~z0peT0u%iNjM|aZ6%0Ld=cy=M} z)R~f4&W&S!81(mt?u@}mCFPBFINS#O8(Y66_af*}LjUK{xhr4p71{;%efbY+Pfk7g zr|pw}-Pj;GkEa6YDuQeTfC3N;7&&vCyxJK;69>rMSrK&nd|m?u5H@436y(|F7dd{m z?~d31{O9s&`MoFb;{SV3<-g23$<>a+|2q+YGpqGmyfu6&{M)ej$anjYZ-K{8{Pd0dh%Anlf72N3!NnUz8U7+sqZ< zzebffIyPs?9rl~BcebF-ob{7_TCRr3dVl9Em;R~e&uGAw|5*BsdVvy5%~&l+A^~M~ z&~UD-^+7$)NLdf8eIL*lBXVl=AYT6svv-#-kZbj;luO2nA^(`jy^O;BNA|^ugPH_nKC)N?`2_z;_OiJf%^9hJV?8akQ&k^fd$c!6P%i)8C8#?>&|OE>BN(alF_l zfU>4l@Sj-oojk|?FI^9L`fP_(#}WmoP026h5WCNL;O}G7OL~#hQx`=j=r>yBlJ39s zFGAE{XojK$y)lCRQsl&kzw0A59+zR1)dwLaFf5?H*-tuGoqG9ih;}TVbT^t++k-y) zQA!Gnt(X6x07i{zD=2|t&CGw!ZD#|@?-KX(jlUf8jA9ia(8`{OX}=?+d<*Em`zgFy z|0_@6#s9zZl>WUs#ozoe=&$wv#c%xkO<4Epo8mW^4Ixm~PmX zU*wx?yifiwxvK(&8u(gKqp1^5%`jL`sPA8jn!=W(VpXBv>q)PS?|$s8j=%c%eiE}XsR9MhxjK-m2Tl}VA3| z7r9Nn+G39^LzscWf>CcH1m`C(z&XQ1Y@~?X)yLTwpVVxF64p-+%==gq0b}F%m19$ zH-%;yl2HK8Km1$3y! zV;117llske2H?CCR1}~m26!pJPt&Lu|M3%^tro_xaj9W*NHb%VRefwQM z?7iNU*gM~4SM&n@bI3;zbUgf?>9_o7_-Er>eo1EYNnGG??N~y8V*a;yniCiF#stbS zR}Yf8grhk2>zoV#!|ss0cDqZP_ZK`+$~)AH;gmh?mc#)6Wu&s@jP&)L*X z$QRBXoAdh8!JcU77VuwuOJdA9E4~FNL<3F?peR7{F2G+3@DoO9(C3T)c?v(?{NzXC z?G697%+0$kVu65v@>%(d5BUhVDxv{H4eEm&=r{Nd`1er)wy~P%AA%_x-~SQlf#T!G z>X-OiVa=APKw`mh%vc8)tBD+!tAq*dV+!-VX6U8da-J3QfOsl^&r0Cy zF9ZLCQTpOPe#%n~{J-3K{(=^x+wg#Oi=6{B3J~KH;Xgm(U5UeunRjqLIYP|u91~w} z@Gp=X@ZUuJl%oaCmh00emwafiFZu=Q;~&hP8W6+}^`Ts^@xVLj=eINLzjMSI^kDu~fb^Je`G8xf@$8Y%eqizisIRrcm&gb`*NyLOJ$*ju56BnH|ABu0Tm2Gfgit@| zvES{X>%SGM71H&o0*MYK_u?BBD7*_A&}dc7-!Iv-THZO^9-7D?$O$O7RRCQDP|kWa zMktxQISP5kQxG&D`1}Fuy_^fjG2uz+GjGoFh%W_5Rss5?KjkMC|I@v``2TS~f0Je` z*^VbU$sxlD<2;FWgBaHjy*d0hxylTnXBBWf?ng577WBYb2mJrodjspnkzMT;-L_;) zvU^fMC?zVp4xOVRmAvJq%W_wQK^J$OK6k0{z zx$nR5uY0_XnE+!JAo$-Pa(@>1;aYX)KR&2Um0$j~ufl)+EUws6kHfk==|%vy{y$D} z?sH84XWXy&eTTkg{;w{vBY?O$zTaTg7!L$#VMJiB_AT+>@mkPbhe!8~`u*U)*%rDG zs|b_MRRGT0xj^}5TtRPkvGvCWyJNx~#5@7qNWhP^lb4r(W?3)a=!C=nl&@tx;oQ*! z+r7~fU+eYox{C1jNlnSb_Z!w|VbUUrchA%0)W-g@@xQWHsDYiGGP0W4ycb~nR!BVL zeGKqHeKp>%bLao}w)pQ-`bGA?TO_|<>0k6~KgL;}^-1XKMqf6bYmrCqF1%o$g*kV- z+NGa^@A5SK7ju9%GXQvhe+fnc$yi^}fP#Pdro`}xPtVtUWe>XLw_sar->lJwVE=bZ z^q}AQ&zU{E<~#qwrwhadybiv|~u}&Tl^|JvivA{h(0a^SqxP?)3M3f&wUgyPkw6f3Wu~uvd%+1W&Jw z0P>|6`{&N~H|v1k+6cbk-wys`qkcX3?_&(muZ0@uZ@n5Y>xBQ@v+cE*Taa5H{5$L` z=8XM|dug@?|FZo19D!(C#uNVaV(={FD0#`2`mHuJ{ypG)@@1d)&k;S+3I74%#z?3z z>VEKFXhP5)O$?yeTT0;mZC^J6a7xp%)2d(eLwbGOuX41&o&S&N3&yX0>7$>L{_Q7! zH2Q4LEc=tutI5IJCH{Y`C|4(YQv80ve-R0=;P0C;zs@)x?;rCNfPH^S=oc2pmj`h` zts3M;|AHPg#P{;=s|Na;VGaAU)Rch#o@a%8g530?u&MA{&;2aWUNibw{$mxu@%@et z#6iDP(nlvCx*$Dw_F#_M#sgew{k*b2C33Yui3_Mdri_D}mI>Hz>RzXW`-1whUjFUi zfA-#TzL%V`f~pqJ0sBtwhql1q@&7&l|NAXI^z;6&UMe^iTV0di ze4_nt3jNNAph&-&bDpCO`dZ92%Eh5nP=JK%LIdKWZ!ri;ki$LexHQLu#aTD}Qwjx8 zs7|7Q$sJ2fi3G;xnzJozJ|UhO>KpF+(&gD&^U-k)LK)lWv9a9#2F~$P@YVe;g3f&6By7T!7+eJ?O!2jEx zLBIbtM~gDDN?=>EUi;>M;J;S^6yAHOFH(vkC3|v-<=a^g|4vSAgx^H>2J5!R2mCwHzb^kW<9aOL8JEN33*P@NUGECe=ti(7-{i&NKQ>O+<+@P- zh3z2nckafyABF!!2a*zFxh0Gv0qeptdB)bU*-maVGH@(55=i3yUhQiXAZdk$Zac}w zK#oqz)8q}^F?6rjpS7=E&{inG+i{!LBLZmN1JL^%;J@KL{&f7#|DRjrAgvRu17gJd%_6=Ne4+oErTT+Cn*Qa6OjWJ~eH0 z@Nef{`W4m8X}4`tojHK}Q@}s*{qK77&+-3vk-@+Fv|shO@PB{xv0=O5J?Y~H-3kAW z?^5z?ygvOq*iNaO7{>3Fn+d)Z!};|uX>eGW?G zvtD~5Zl1y^p5NywAg1{GW(Kf{`7x-kS3w0F9q^6*0}beJa*(dOE%puiW3@n66>9nC zoEN(_9NU^d7rtNc^9ub5@s5`lj`y=|x*!LF^YNI{s1p{}so7=jGqd z`B!KDvwQdzN~g$!Jq9!Ty}g}&J-+Zc$FrgVO)pP)&!K}7js+f1&-P@@04AevyUMMv zb`!?^h39u>0P-bXn|Fc~|6TB(8wWV#8`c}&3o*Vxzy4Mn?QahK@y@@K-;|GU&JzH? z%Eh79SQ7FD(|7)zy+juj-rtTDj3)}93>D>zvllQ z1N^hTlVA0*PBAlI^yca34e!dRTA1I@`Q>X!k4un>>D%<^el#fX=feLQ3Q)uV!czcy z>6<~l;Q8a7|6uGND1hSsld*vO8~zpY?Jz+b`qf_Is@y@qZosa>bwjt!eXY%YLAY>a z@&&)2n-fs3EVCD+^teWn6Yw7s?K~ySD&+U_PbwzP zv5L{F3*V0jB?1i~_hifg8gl^ki!&Ynp#J|2|91uW&s*A#cYx%n=&MN|P8d+)e+a^} z>qX$D=Up3!f~2R~Spl2g|K#xB&(0ix%_xBRen9~W1@L*^$JX%nffle{`g3OkSg-G| zRRYD&PmUk{fA+^@bwGa$MgVoSODwqow+i9SR*3Lp%^<#)|H*OEwR^=m%6$j>aVSBM za;)YG%fGC$^~uhH7R0uMGahp2mq$M(EC(%<=wB}XiV_ql`N7xAGXHj@P*P{V;6L9Y z0Dcdk{s95;OPBrb<7fNlmiT_`&-wTLpJ{>m(ho)tpT64k+Q82#J|Au-27rHiHevzA zjFIc}S{F5FXfbv2Gl%2W7F`XH)qy#~nE|k?F4g&V zUqk=};r7nIL$Y$GN!-$uaneGWsevr*bEa*6vYpiI3o`&p^y5o%Q2%WS#{7Z#qo9rY zluNj-MvP80j0cO7FLTD8mzx`>#NLiIO4<8ez?^Sdebq1ek$<~i@>+NP;{*Th_QfCa zaeAxMYXk3}@Gnr)tZnImE8g|@o7{9Le44V(88yEO2898IdhMIM#!K)VfbA-PuIF!b zKu`cX{C>i}LcS2$ll}w$eyD)LzU`5wOfsX`j6fPMo>;-X69FinTZ}=16nWgM!Mwfm z-;{OC)_hgLIXft+o3G2i!GFN;n4WWE_GFern7xNz+9Tk+VY(n+NG0^=?&0I)I_0+_%AeZxh;JM;`2U{&*BOJ4`|SUFS2ErL)JHufJviyrInh6!T`tCKU-K+5&;f%QgEzK?O#hCwhxR2#um;p%g4DJoTpKOcqKtjKx0tV;3ae%M)_k({W zUr~YNEPG8x0LG^m3>$1Gp5L(9#`53XqaSzZkS`pqw>#w=#q3apAl+!!u z?%|pldxd;q{$FViePZEQ!8Qw8@RqVw1>u-4>$ds6+EA!Nkl(8&#ydd4_p6(Ae60^7 zA^s0v={wDTt>fPxxzGInwYC0Tj`4p#`k@~E>=00=nx}vG!x%c?zwrMGAp!rw+*%O{ z80$VFXXlMUz3Htfm(Kxzy6*!U#3v@uo3FM0U*GvB=haaFKlqn-pKt5`<=y`a+7Mhp zUYrqtEdM$hfZz8+0b=wW~mib^$H{CmqF>>vlf7z&Ja^&Ej8pC)pUQg!yo3|pI zaeJNwjZ1vHto6y@Jt!3?UH%)=X*Xz}vz`3L5~Z-o-|@e5Md4~q1kn7_O8g(@_G9Yy zyAS_8_5I3s{=c`x$83!mz<8wh5(DR{`JJzZe;Hozi@iFavk%#g1{iPo1uW)9-g&8O z@(tbKt>G!vC`F8KW;77Q0P^PTQ38d2U;l6T|0W*DAsf%H&~CeyP1(}ll&yURkdzN* z0OV>YKyqjLT+hj;I11pqZoI!ie;)iBtT!6q+)?A}m9w0@m^0^m?o~%eR>y)GCj`gN z*7buD0TlBhjULh}FfV98ux;2jD9>fQVO_{Cj!wR_Z_L`4Sri$PypR|D**K$mKr{<{j!MM^+1*wG8>}bJmO?=667dJLHnfZ65wU;J??` z8)~3vM8UVMM%1#dGd96w6~I{=?2joX2mf(yh)!5lV*kSX7p+m4;L#QOsoQcMo8!yB zke{@i!2cN6*tG9hz)!R**K5Z9%9V>50J-!3AL!rTd-=aVav%T4f6%gjE@L8J?=arbkDASF=I+bEfA8F1FvBU_dE%>e`7gY`F6D1bH&l<=&<^)aD;3_K_9FJL z<-f@l*l%)^b~MTfdSXy7NBm#-f4%en4|?feBY`{r@ekVXKPhWpiWz`78GYC3yNTBy zP6{rXC0PO;Xfy%pV7iI^bKdlWNWb|@5(BUhq06%mBU~X-i(vks_syVU#!sO;hj7f< z25Qjc7<;mF|MFXsXPY+se}7Zfj(R9FfPwB9{#}Rw0_%+c4Bm@7YxK!?OrRV$%(s+p zTDTYPhPcMp=gTU<@E_F9P`Y04-*{?=*naHgIB1VL$d={5*0bw!u8^NBjy4E6v(#~( zp8BzfB$TTaV*p$K?>qs}5B~eb|KaQ1`TzXCZi>_A`?2Zgf$6!4_8tBUKdumxFd{If zm%!)7HSzy`Q;08=piqK*DKvnb3uR{>fZqzPOAK?*$|%5JlPJJo-dXF?@2qtg$NHN? zy~2NxwjBcq?2nN%PZVG3<|u$%b(96GXMy)50x05=oV#28<>K%BC*|yj@{{`Lu_qh- z%ZzkpbOYIeukLwDx2!fQSlb!_&u?@$+qYCM<&9##v9FwOu%DcfyLNnk5mg!2j7QO`o2Y>9^FP^fbD!mio@>~L zGQ0XU;J)$v&Zyi`0%aCZiPHThm!A2;vp)x=8|LC-w_+Zkb`0R~Z_B@-1wHXT@j$KD zH)yZ7JQP6DhB?kL-T-jET~sK569F_-`zk;X@B8it3gGYjE6f{Z5;ItDe1Bow+v-}4 zvaK%o(w~R-hgXP|Q=JQ*V=dqPy5Qvv4Jh)9+)0lj2y5H&M%*78{c{bJMDhO4es8)N z|JVK3fc_%|c;h+#QuaINe?4-~`MDNwQ6kjBOZnl~`YcOM6B#!%dX1M1o(0$?@VR|a z_%CJv0`80X03j0ajR5>bueCFQe(w1-u}h8yT$EXWMgij0pxzJub@@*C&u_XuU)STy zrVaiJ{&k{)*O=&nQAc0(s$A8u+L&LDcp!E>;HZJEcemAuSjubpPipaINhF|@%46iO z>!EE%{l{F0Lv=07fg;wLEdREoCpoqX&?tdpQ{Lf!7KQj+?=O#a%3~&=_Z>jLKLPv; z)$t$a-1+~{+v-0nsS;lU`uow2p6=&_UA@S>0|fs?j4Ts756yU4f&U}|5WK$e9I(gW zKVJMrZ^?Na zy&*g}#?hs};am>VWAlQMjzPV!tvzGP+tOb|{PH&T=PE|)zU>F!0*L>(x8FeW{P65?fvPwp$xgV|FQKqj(B zt%^lg4SF?zT48l=)U2m&%<jpswxjzj&&vygJsefv;E0))K znr?b>^tT$lcun{(w8oeN^ozlHL;P4D{Lj8l4*WOwTHLMj`N^F(icnjpxi@1JKn>3c zn>nnW>MStsqg%_rLUfW7@IU68&Q*beb%B0|{INzA`Un1V6(ykF`2<@9$mPFg4&Xim z@Y8yA=RbbB?f(AE{_4}OAN|z${^{94Y@4tUP^9~If4|KH5I5Je68q=!ufsb&UuG^4 z{+%&?FbgPi&T|kc2u}oTwV?MopyKU)*;m$y_pfh)ho7{-`r9&V!)CuJ5j+KKW*CGS z2WJG3JnhqEU)buq0?eMke=Wxe|GtraE&qM!8MPt~*qT!7d<*}^JTOlVV1|XU@tf50t1Lf@=g@42UEBrU(1O0m zi*Ym<1L$G4THwS0xz^uoneUAX^qv2t9Op28h>0?;z5LsfuZIfc`v{=9!-D@pyPP`A zTGpD{$R1T+_pvzz+W(8!;qjHgSgBlxG0G^xK=izZ6kPvIzS<3bir6!2T>oj;z&;?xzOSQqBgK z0<}T3W5YOaJ_wpON2%?(IcBuW7)RHVHd4+Nj@D_mc~a7eIVA+rZAgDKu3B8DH@ArZ zj0m9MzwiGM;3s?Wo&WgBmiznjS_BVR^mQkSb^K;;-%gk<_5u&9c*UYO=MoK3S7Rg| z@Go$0FY;P!gAstizrENs_^m)c_j6sGRG;DRrtmK;v8Hez@ZYZi-;M&zw$}GIYkTM4 z(Fmhtp&rRNqZk3$+!bqQnRF5B=P@IY@b4SzE98&$#{ae&pv%AGuZ6f=;8xE;O3yXl zbKO894O# zyFt8@TbvcNn37IXQ$}b#j2OQk>XAH3#+FWw_hT&o!ZX0UzXR}-z4*?5{AA1h{dxJt z9`)SNAM|C}F|g6Y^Bj->yS!x!UBeR*fH0a?G(nl$i;4X4`Udq$B;ZRwsaL->BY)1g z2;=pHReRP;-GbP^4*m(FO_oX~ea`5l)Pb+b( ziS}i#G{;V{oZDlf0J>#!&#fQ$&kz4A#xHTjJO3a5cW(GMdTP^uojw~7Qqyno_D#eO zP2*u!^390?0u{(t zhw8~OEHT0Qo&Q(`Ftk94{(Z`Dro0w!0p(I3+{L6C_P<-B0{H{~^#=v0iA1P}oOQ_k z&NMZ5#x}}%wZ{X`ZIsIO-3I@Zo>4u-Hx}d@CZ~F=<=+mu0R07th~ld?|fI-URo z-vX##g4X!(THg8p_%?0Hho-OATR%M;yx5bUm54vj4i&Z>*R$|ke~v1wDVk730Ez!^ z;{JsHKm}@5z?cKniTp1`c^JLY_1?)RZ zai+fNi|hzMxd-Kapgg>Ptp+srS&&?i+#CP*DB9cyme8%MZw8YInSSnbPfu@E`2i#jXO^c~h8A_%{?FQ2-tP z4x9_ztDlHbHcw+^D)PRC+*TG zz^un)T8VTW78}?1gP;OAexH0DJvdG4#awySqTb&Cu!sDjzXY%G!L_{e|IsZv*VC`` zYxvhOqJG7929duob6-RNMUKN&T$Gu-Mjs5S{NhCRf&wVa_qzg2S|?6_Nf<5424(do zw|?%6yl@1P<(m-6%TWKyvUJCe0SfkW#sYYLIc$!{@-MHB&h*WQFBAm`A^=$}sDuAR zn`$F@VI62!z`u=}!M4HnP+9oqf_@q5iSxIXLLkB#RycPw`AX+0REIR-1(26YPWxVmV6DomE)xpA_9+J z6Z^xtKQ%fq`vUPX(L6^Lu6&{a_%9Ry9*O0fXY+~*^zQj=rvx7 znLqtzXoNv^z3TEl={%zUB-JRuoAv5LpcTEJ(u_EQ2!QJn0W?=nwl#OSHYq{F1nqG? zGl>Q4Fqs-*h91aMESqtvvyM(tYbAe<)bel8Yv*47Evo^&UO(Tshj)kcoVWVmzaNaF zzWnQ>5L10Wzm6FIJKg}QJO4k`kMI1)Pqo{>J}bZUgV1XW`i-^X;}hl$Z(s1=2mgNY z`EDHi3tR>9e_VaIzBL}dc>*9r0s{Gkm-p9raTLI=d1Z_LuS*aK7-N9O^T&+kVZIp; zIK2Bi|FwG12mkt8!~a+kHE>3Xnjr$#SX%;>BcNCzO|EYnl0vP;r*HGQ(!r%9L z`=(w-!D@YcF89h>=UcuH1@&^8J&r8@`B(@2ihdL= z>zr$=7IOa%z)$w#JOA;ME%&d^^Dq98kMvM_{Pfc}3xvzC^OW$fn=T^hBuC*!hyWD7 z>&(8f=KbG>d4>Om|0L~<@QpEmpa8L>1jl1HWa?F40{#<)C?bGH2iSxDU|mP_|CSQQ z0Qy@W`NaqzP!~l30{-pV#Q%ON_%~vKf_-O6%%C~BQ{9?0F};qKZA308L+!r9rlJSR zS;`qxJ6Z4lR@R_ji4p3Ejs%7S>J5(HtnvKy;Iy5cbf5jlV{^P8mDU{N`tV<;IQXvz zx%2;%z4*?5{AA1h%k#d(gFos?preTZ6o1%QPTah72wXLc3{`r>20C6B0{i0d|J@1& z$o2n$0`yDW3XM==0KLhrFUh>2-;@ZTCPE+rP?%04fH)}cLkVWTHwGwDxysi3h1E#!F|@~3?qO>sm2=K4gMXLg%}`)a;O&QOcc(jpUkYsDnL_$T+HA3 zk5vF!{teEHXkU-|U^q7BPZU7M&(E3` z3$ECV_=|Buq5^tyt}-@9lCzC-ov5MUKR*1g_-kD8&i}{%o#jh?ReC19lioYMxahlz z0yzBp6a9GKjB&^{xT>c461-6) zH<_59_(0j#js%qG-e5itE7G=pe{zDoz$gL#y)%Hy7{IRu`QJ_XoPcHLK7;%1jr1GB zYd)8{1r!H0%u`aYf_9zts1l`vJ)wSu-;OmW0x%Sy_DubO|9Ft~|MjRx8R7c1ocCiB z5yaWCw{zp2|K|PQyz~E4EpX>QeyZL6{w$Sb^e^zA_^~?a_EdT+Q&anJq5xW&I|wl zcD#uca`_jOBB9r?*!ED!PLx9r{*!O<#k}V~8bF&VK3x^kmW1gF*v> zly9DV^SA3lzudSsV-SUV`2+v{2mW*JpPD#~Iz;NZiEw0m^EpS$I6fBCY^;CaKMuNU z`R|SYb7%bon)9)_nk@UtmB)(3px?+%&N9q*T%dEtefPiLy=7h&x0 z^y-3t*C5@NPWwPqr+0c0&&W{+pTq zfcb=fd#)??+?T@h2cAD+e-`8Eo~++bNVoO%c0>TR3Xr?DZ}qCL@?wx*SG(EGug;x~ z85+=#?^tl3y}n&GvX`f+iBhYn*(^2Vbw&YG&bc=|)<){B4V;#Uga5{l>#3Lj!t;;i zLp>^OrjQn*_a@ge@6kDRu=eo{tKXf=wC5@nXBFT|Jc8E@&7;Lv0-27r_-06 zUYqOv0vZxxnx!UZ-s6QTA(of%k-QfSYw#ae83n-q8}o7n_x0lJ`Na2|-^>9hbALAR z#KmrMh1Of600Q%Mcz{(I>U#sKxG(68s_sISFD z|C9`Lpjcw})5H)j6pp&yNYJ1q30yv3~0-shpm;6U(&ISeoh z7_j0n7Ubv72sq<^WfrclI>h7mWuE(E3jeHMg1P?MF+g#}c+N{vK4CR)v0mTt{|4te zv-@m=W!8-rOn#B7jR)S2%+VsoNTPq)#jcL`ZxmqG$)^OI8=u#(ADA!yj;~M3C|XqK zJ_)QB8fLuvXNNUvH?>acY}l-ovIWP^6M$saFL(T(jQ;B}4$75dV@kQVvPRnWEJIs; zy?;FzZ~w{GjeI8N-DiSJcbMhcQ@%RQk&Ill& z+3~)0v1{Pz#}xi`&EelRM_-%wfQHe$Bv0-1+FV_(`Qqc@Iipd4H*=`Z_4Nhyj^(@m zk5z#=uHTevC-f^C5Zps>^?EKeAea9@0rcSC*T3i9v!V;mrziCia&$&8D4G%VJ8Rkc z8VW}%9Hs~V2H*M4e?91>PY%8mc3{2vX3*>f=nn1^Ll0s?!W%^(|!2PfBbaY{ZD7gmv~T^ z56t1eiDr9zA_`#mPMul4Cx?gJm>}UMo|V}=#XrWx2v@)5RpDBhi_4d0^zWErfyryW z2wTHjH|G`lQP*V5UT_+>BI55YSvw05L-xV>u%4UFjREpFR_q!4E2`4_CSWn5P!zz4 z{+sx|Q2>9B|Ea;$ggfqy|8w<0=Xu1C&T~w;2xFZwKm5O}7Std3uO&N|Y)LYJK9LsmpA@DF?$@+W9c?LyrTqy zzwgH)RcOFlYD(xv4S9d}=fCK|cmCtQXuZFl>&w64+k;-78wm&u6tQcXn>a1t`V;($&X7$5-Kzu8%&KW2`+yeh^L;wl-g#svq7h?e1@qZpv8{WU3drd~M_?^>! zva3xBwd43|z+3s@KaLTA{=on6-mveOJ=$s5X1nI^{3ogYfq&bve|^<2`k{Zp_@%CP z=l^5>-X`*3C#RP?eAkXz6+6>$RR%)B0Ehxw|3Zv+sl1aT;W;s1Sg z#fdAFdob=P@#PKQ3jh6_D1e{<3Dvf2D|xjha~I5PI9g=zuRGf@bz&J^tTk+uY>v~4)AHkWAv{Z``Z`&rFe}GuI-)wk8M%q`F}qa-o7xS zZ|kYg4i}`y|DBXQ<9(lig(l_D;duRqo+2li&(o_f({&4G`|`z>%wX*k^~1j&q{^!h z0SK(l8FFn%^_#vr%ugxS!~)H}uL8Vnef@tNV}aQQM{}>)C`-Q-F^Dbyj<$@+!N0?G z9x=Vnc!jt?*8)uwMj%2glRH~b8|P4OYN(uL)Ntx;sNl3uGCES)DB-=C|FbdD!k(}# zT2D@Oc>UQMn;fT1@ZO(&Fc0yIUGL8SNB_n9OKh(-=r1DMdbG3A@8uNu`#vi^w09;T zNDFWNK*_U0O~Fq;k-8PMIb1pX*Nd|lY{t^>tQA6M->_t^zL_(~Ycg{fm*)Qj3r7{6 zb@{I;xrEzB0*)5s5dpkyC&!2wV%UEzKEXEv=owiM0ocKRZ3JMD97HfRdd6&jt*5Vv zcX*@GiCq+}sErkz8c(S__*eD??bCz*#G?<-whG|zuSLr_%WIH zm-}=7|5C5+{KtRUf`2(5{)7H%v(Sf&XrRvx|BB~V2x@$0Yy^-S6U&UMFM2JW0V+zM z@UM{9llvmD=s)lu2mikOv*ww7q8SEve)VflOmX#>f@FbZ`__~UwvH)#3hN2~b@>0< zeB;=ZoAWqp-v{<<9sJue-Q2lw?}7ph#R1DseS#>)&Up^*z^=Y!i(F+!Vu%m;7s}MM z138@c1ONKYf53ke2Q;Sm_{3s0Kvx0km>E=Je|hjb{r`2}z4IUcbsPTi3id@FW&3x+ ze?R!6on#_j`0sNarNF)m!?A}B|nc@z+_J_B?1_0mf&3B8v)8)*jkJp zt#i$Yg3sumd#k5HF~*Gc>5cyr|8J*;Yu))g zgMQy=z~Db%-*=mjjU_oo$$C(p`q>Bn<>D8--ktxC{;QW1Gk^u{a@N!I=6-ZWx^@aY zq|MWQxR4zmHx!@&6C30A+xgQ8g66hz5kSY|cCQ58B0u@N>Bq#`HO@$6hS!Me7B7wmLBE zD`om?e(_(B*7)!`-}(R8HZ|S=bjJUF3N)Zkq>uR3mjA*_Hc@|U++0M>PITPF?85jx zCq{;pywtU>ay59en=}6JRRCQTaLD%Y=vXYRb@OY(qz;M90C1Ig3h*KvRoIqqQ&Ld^ zUp4SodFQ{#o7_YzDEF;7r#aKcHUcnc){OxKmEm(P3EQ#!Co>j&Cb~tPxLc=$tOCUH zFHkR}e50N~p@cfi^53*jK)>Sua~U_{r5pt~yuPmQkELEYRudiY-+uLremDv7OJ4EL z|HuB_b)Wwi{LA>Ya}%r5r}1J2|2k1Jv*3omOhy6@v3BlrPJ}$M^Ng1v#V?5(C=ozk z`r5Zx{$*lv$d9GpmjBs{*L?9?USMsQL|=5j6R7BhL;cv4YSISl^35SV@c*%E-x9@D zr-Yyc&6&o2y)+R(!@gpM4%B+`gy~rRW5)Jpa+$>p?pDwSS*^(B-=;RCbQM53tMUJh zGRj5)wfxuN;|<>R(P2Cw-I(J`T2I&pX3Y2np5J~+QsYx$Vf5oY}KQ#goU^s0CMKm0HM#U7RC{BlYne`9SkU$y)PeCkBXu%G7| zZg~C|%nLZz`w!2ZvK>Qu9$q1bKTx|0SmHy6~KrAxbph{!jl_!Uc)g@ z9rfy$^FQiKE7c`XBGf{;%v;^C4@oG!N0-3Va4-6y~&F} zWf2F=)`#~$@Nc{apx4^i-zOGFVf+#|J@{9+uJ!K@--6%&-2yGJ-B-N7ay;O^(Tx?$XQBrinr3CBJPc9D1fn+TxcURiSa-&%1NcLni2BY!lbXoF$|P)Gdl z(@u=@?Lm)cd-e%^KJM3iZ-G1iAKe?|i$C~D=;cLk7W#8OP9InD{M;~a(5ZN8od|gN zcnVlhQiU}s=)sgZfH?SXo&y>*HeA~B-|*=W8mkA+T%BwL;6xTNPZ5aK`$~B{yW-pR zTf@Dse^+R?H76DCPXZ=SC|COlvg_a}K%+%*@E_~jeIoiNIp;~K)mS}GnW7dswLl{r z-hH*A*Z`&-ULLjZaM7`TywFHLBh3W=4<^fDk|DjHdDYg$DH1Zhg!3{DuY?TpAI2p#ZVw ztoub@;wl$GPn0W-={yB^wIy0%e{A;jFZ|bPfFJz#DuD7SGBW`@1qdPlM*(8vfQDp2 z!3_SVrZRPb|GWqa@Mb}oF@t$wvEAET6iPVLiIL<@88-rmRRH~i|Bp}X`}b>y-1-0b zUeKaM0I>>C!~jP8Uyq{!GTshWgkxiQZsyJ4gGe}u0eTpkxpZ_OXATh0>%807H{~6G zT8}R~@^`ol6d{H*t3aP?QsxAG00jJG{?cU~k*p=K*uH87DMX zb@*>SHC}onfL!_m73lTq!Cm)Bw?2I`)JFOA#`iZGkVgd2$9N~$kIi>t9OGOgMiPAg zr~vH){nJ*$_}}l}>HlBSy*vN$;VrHF|L=HIyt~1NF$3r`zGci?@UP=5Ny%)_1@kdu z{wF5}Xy)wX(s*wjZ;R)Ts}Thl^yl*K&@USSC<@Ro2HD@u=g5o#;Bc;DkeAE>Oex#3 zza9KHF+sd5fUgeZMgZ|c1W;VL_j8Tsi+*ill0uhky}WVPb)Gs+U8oJ(WsCp>>y17F zkFS&p<|1Sx0Xw3Bn&$-@1=u_P`2+vKFM8UC|6SsK#lN(`o&V4175PQ*_d0yz*NZ-` z^lpXzVWk-h#PaXwg!OovwWG8@$JH-}mz1IGTJSH700b|bn82|f{QLO(7sX4*njfkV zcxyZ60-Yy}agm##9gYGhbo+XES>}!XAmytDwciPH#t2RP-x~ucDibfp96)g=&8$SB zOhSy|6Q@u&-`eykm~)UF0m$<2)XRzdeV=#KMgh&bW@eHRN}rvUn6qW0fI8*?g{S>? z=l?IH#;4@m`TvY|ZIOp!)-NZfM+4;SGpc2lD|XZyvn4uk<^dF+*bvd|-5*Rw)U z<4+4yEb-wD|BWYC%$9ilfqzE_?66|QF1O-3K@7n4FUkx;y#!PX#Ot>rrBH9ne?ouY z{p*MT{1^iWgPcMmT$&;NEdIJS8YE;j` z5#jCw&p$Pb_4Gx}oH|ZUuJiwHnZ zF?>V0;3bQG?mNbHd>ws%M+X5Bj@8g{E_`Sc`!@_Z{M*g(x_zmu!+){J;p2Uox8*-J z8kn`gzwQxvRi4{7*D**GM&bGFS9!4=1vFd-DO>iHh#=rUjyNDUGLUa?r5yeR1<;pZ z3}D<%G6u-KL+%`HGv+fC708n@n_(QOjr}?xe z{`En>)+hD&xL@~&7P#~OIsHMt=!4L=gT8O$|4+@HZme974gZNQ^ts_*v0DD~9K`GX zoJ0ZfEJW?YoG=5R%f7r8TU!lqJoBV{dH5{*4HLyBMda@+vT5T9fDoBw~%`~UR%fztT2^qv3DXEt9Hwi!+LC;%Qe@Z7!J7oz|@$TznBPQM{b<0d$9O10}#7s@GpnrI4MH`;?|=lD1dW^!jsFPJD^!M0uWS2 z&@M$AsL86I9sCFU$BzArHYuWhMFHxh8v&61fq(y6pVsH&e&zQTxby!xy`iMO#$$}n zA2#5i+4`xO;~#?r_F3@v0{HS8*_ubd! zKMqZpZSSmo60`gGYQ`1y99OPD^q-I~--;1_uj$h7aGwYJPEJF8^2tVdmYG-FQGgHw zIQ;9}JGF91u1AAqX9Sbfuc?ndC7dh%zt6^gF8_L{LUE38W)T@FZDGtJ*t`F?`~UQK zXEZ)Bb?5(cTG#m<0DTljxJ?hQQ$m2A?Nmg_`o;$yFxgAS<_-UizsymI$=mUD!+%~2 zEzp~+-#7U8mqrN$8XGPHbIjHPPG@g&Cm(iq{1B@pb~Ajl-t>*;S6u~2_6iMfxc_cW z^gsLh&6a{LMq0wG~RI!D}Wj&X5C+*99ysv#@fWG!MxI(<- zH5gUoOHtB@01O3~vwPF|J_i^aG59x*_I`4p0Lq9$vAhZM3f$Ml_)jJV`9*|dXqyrb zj0yz4U#Owu|84iF@6xa2Ci%bfPkGAdIZwI~fbjlbGXC!`dZ+Z$Tj0)rd{P@XzXXyG z{wUBNCm*h1gnkd{fdb@{lOy}+6dzLTH${y-a*a#L1lojgVW3IBfo z2GA$<%=qEDcm98DjgL&Hh7yKbNu$tT2qe%|!vS4T_aDe&8c$~hx{L$`w1MxJbazixdD{(b3h;+xnb zJ$f;^hzYqxuFpt<835r8KzZmNdTHF#pVR_({y(d4CHyD+8*I?i&kEg2O7Qoxv*QW> zLcg!G!N1O%vhd=(N9#n_{ao^2 z;13G_1|L179vcM+b@E+iEl-Ue_`!NVI&C3mMFINJRu6=<69E+M zCST(ZJ@vl#87*+<|1({_5GIWOjrqHR`+l+4!jl_R7bMH_Z;+X*0CB7xTBoe73Mi!~9qYM;w*rLg_brbI zV2{K4m@W0ODc!ZK79>~FjTwR~=V%ZLP}jWFQ?IHS{09+$a^JxSC#j<@{R;a=x`}kA z#(HjOm#O_zp#VnQAKOYws#XDl-vEp||DWChcm6-Cm$}4O8~T|8q<^32=k))g{|DZ( zmw{&AARwSV7z6YZbP)eT6^pO`AO3yk|B~^$LZ3sn58p4cT>Cr!zN80h#SGIUCs!Go zD+K4qtuK0wmwHv`p7M6g{u#{2`#S(i{NJy#>)>jI#eQ<20D_X(HHiQU1rVNOG-H+| z`Zq>1vCKQ%%M-PwraARI_&3_aQHLg{iH{twuWW5c1kMPc@81FVbo%eV{}G?o#s>e+ z=)X1s2ngv@p5-awU(bf;bJpZc|A+eG-T4}pHj);YH};+Rzh?fgZnESz%qJ?~LpEt6 zm1|DQm-%>48T)}H%Nz~%=lnoB3|$efRG7lJ|H*=Vh5WJFPneQTiaWUpDpETOXh#Gf z)QvhYjwxn18a*@EpQWkOEY$okr45?eH~UTtIOBdH21r^dP=MquV0*|<``g6*%0~;_ z{r_k61rvvVhyQxWMID8sL2|wGUr;$YW4gr0`+gqm`|=;`&H2Sxz!+mFkwu^fv5|li{p*^M8uNV? z*a!b}RYgCN`M}}msg)J}1O9bK0FGBvdiV7Hq>uZ#nFVb4H~2Tapv@Zq^B;VO ziv_Q5SD7gK*3b2XRdAm5# z0G&8sBmI6AG{Dy4;J-F6xl+%(<1M*AV}xSdW20HN6u;Fahy;Qtz#xCDsB!H&SR0x! zmeiSA?`%M<&krI1MMXvpoK~~@48SM#&3M1=o&Vq4;=@z;|DJaMd8@^SetNbo34+(p zedd>EWn9q14>SJ`{|-HJarkn`vggFpDtik1y__lv&@V%@uNK6?wLvEo?-5(dC+E$A z{lb!MaGr66dU0kKduAGlG5Qun_r2FDwzA&ecV0lg)$(6l---K^^ZcUolyAl?JnO4N z0UCk>MN3r7@%`V~udR1dH)^>@V%gW_KWP~~G$5G)tWg3YfOzkx_tfXKz@7ikY1h3f z^M6GI(2Qmq1rQ=z!50Q2-aOg92axcukS}nbY?E!BW#9UdOU|6&OcAPi}!b|M6)p>|cc6vT;0OO%wqAwsU~7qzDmm5e3W181WDHa#nbkr#J^( z<;5<+zTfl*{^bP%v1D<$uHEJmZ?Nk>#Q(E5C3Ts8WBZNnf^mO0d9ABn;^H^il5&g! zYJI-F)2}lk!2c&t{uBNk{*C)g?v(UuSW(A(%%!;9H4I_+f(eg*T*wLg2BHt7Z6aNq!fO`h)im*tf%U&vv|j^90a8z5mBA{t0*fKd%R*DE#N6GN08ChVL&HJsbT$ z;NO;idOW;0KCw8S6Cbk=Rq$`b*mdcdWxwW({b5<~@baqg@5ApwcWkb}{^TZ$y%yS# z#OqTs-^czjXB}nUQqH;YQsdM4thyyt{%d9=7?tGmZ)icF z08Nb&pROoCFb)uEWXpfCezTzd)EMiDhR_Iqr{75FMgTVRe>?%uU!2eQ@${zh&uI*kohJ}T!a{aWmJGUI>$HL3*^DSt`9D# z_F?hGpxMbAQ@9^i^WdKx&QkV^B@UZ2rp))8xnitWm#d=y2KA2Fw_g4g{*}`Dt=Rh8 z2;eHWxXI19yW+jSW;76UzZ2)90I~eb&<**y7Svo1@_Y3#{=~oH|7+g>tOw^i0Q$-O z|MUL(|8HFR&j08Aos!=G8t$DwUW{j9Vyz&;@q&s9BvC(GxUtP~<^Y6Qe}jD?{%_s} zHax9g^(L?$FfOk}s^Q<>`L~0Af8p4Y?MFlq2miUW`^E=$@E>f)462p#h6XfR)5HJ; z?RJd)y$@`CyXwHq7y<=n78 zmfIeV*MBpGe&Ial`G3K`eziOQpV|U<{^Jwdx4tSduoKfZ{2MG3Dqtstf5F?^ct+OE z_}|cio_x~H?|>`bG?*b&_zz5PO$0E+$+D{B{b4ci|C16_p&(Q7_T*!etDCFK{^Z#n z^QAer)a1UIZ|R0)zSi6DDLBw#adu~9%!zjzFu zfYf82WVxSpFZspDfaA2yo7uF=(l4wF{*$p&+}W9{TFovp3RT z943d@z^24oI!PNg`@YBWT@${i=4uVi#+yBL>8r9gW&1@~7xWkB7N3xOMnZkvCS|We znL>HwrdMY+P%n8z0o0zE0EP8Ftd{&)=R5zl^ef~mxlMl2TKfK9z~httLig+5`Txv5 zAYb~c?l_qKyb(a40FEL!v9O>42LDEXZ*mgfpF{x7dq6$*)I1}oXJw?V#M^#TynPL$ zaOlh3_q||WmQ9CNWVZTX&djyhx~v3-sozbJa6Th1Y0z7=VE z<$N@vUyN%BS|Ix=O^%}oatoq>z67Iz*c#--K2Tj)W3wIhlUm2S_gBbQTH>u8?Cd8e zcIYpDC;wAh;Ld-1V*4up2go1%QRv%EOly!3=s*zx(8nP@M+NYEzOg{FZ!m8Y`$Lb- zbHV4jKow*)Gjk4~0;#q*@8;|jAnq%Qk@upMl3OOCn} z9QReQ;`8cdj@Hfs7G(sD=$j1dFZSB2NAjBNJD;p`^_$(2`2g8@!QAJWwXDY~Kpp%K z{bP>VK|fkJ5fLpzS=y`b{|D?7duMR%JO9%PpJ&Oh0QG~?&zljgKRT=zV*rN&XRgnA z?w9K!Wjz1R0O-vBP4v$b^@9Jz6E_h+T>WMTal^8_#-I3?kL>n5ig-E`yMtmQf=^Sqq;Qw#$T&&yXc?OJJo7i%)0!G0o7=@!?6pX?V z{t^BMuw18U(#!iLZQAPaSmFhQWLggZQnEeK5as*PFyMV+d$OsYdu4Cbjou59z%j*6 zS;@3d#=9IH1w{M)DphjWuT)kbpg$OeN?A7X+F1e6cl>nT?{oiCG;jRB_a~JXaV1>o zax<^Sb@UY<%!w)AXx;7qG;W1f;i{kb{wOQJ3gG1VV8sSR`+o>yZf)>bDIrf+{sk&W zb3B{K=vJe>QtgFgO}mquuqta0XCG@CNR)<2e`|gfo`cd{tSvy&+naJZWb<$I(3TbI z(c4M;9INalg(V5*0{+QvCyWnLZM~DN^v4?c2czJ`07?Wfd1@lJ2;g1q-^TxY^KTah z_d<^cgw5VvU4J)2K! zwu{9{y)^ZZ z^wvFrz3&BUFl!#LAB&+knNcWrH1v~o959-CTeoEo;GLDvWCR9@allsCO|S|$F7sUp z82_~PjsN%l{8i>fUIi9lFz=sS=~14a=yqNUGXUd$e{iiU*SzOv1A_|AStoVemSp(xOnSq#(%> zd*e8Q^`OK)CJEfh}Jx#2g;M=t#9=1jlkT=x8 z{_GTO0miK{PQoa~#{WB7sEz;ej-z|jojeDmupg}aCw2h8*1nB$uljT4L&CfnT=QC< z?>5W(Gxwj|2OK;LG!tmI&uILU$JqG)6`#Ax zU=?t{f3yV<{Mx)8L;(r)lk45;O6Q7?n_vxK;QOs2fV+Xmj62y);_0y)><6?ZyeZsO z-eEEK35RkBm{hid{eVd6jlIIZViBloi<8)~xDTQL-XqpdR`#{?RwF_3=HG6UQ5k#> zIR8mD$1ix-8~?vzl!urEPXfrb!M}26Td@Sj)$Qf=E9xa4aJ-@4c>Di^eyRTkg#Wyo z_jB)%lsnxUDqiJg!;V9+4dk+Gt)^R-uGs;*k2%ieoA1X_<19?hH$!$j<7`(~aIw2#zLx z_B{b<@_nV>u?W%p$5m|40l$+i*!UmsG_ue2I!^;TPs2~FfN*E4EI?ih^Ztp)&s)O+ zDA)V3JfPp0|2w2iRsaKfvc0N6|AZum#Q}ko;a{my*qZPfkZ80U$sx`=RY{NttV68* zh1O_^*|h@iM)_2$hg^JJDX89qo({hX|Bk-V_F05y0%2z zoK29V&&T>Hz`P&#!dxJ*1;P8sopI#YPo59OM6vi zpAcj)X%Ls?k0CJju~dzaW--{Ux>|sNv_PlRV(>fK0vP#3Q;_UHey^<1jU_49%g`Rn zAV2A&t<%utv^bheXVmfcw)e%6o_j;5_4!qcp>y<;z)lP>c^?xC5XcACVD=m!Sn*eq zXu{&S1{-|-6F_#f{$x-aXN%e>SpyK+(x1q`SUynb@kN5=zxEByq} ztfznBKktLJw18XROVjP2DGbWWsu~t2IvcPZ zWsaL|0kocQthr9}KZpdBQE=GTiu*5ZqyJs6&yD|=8GY|sy|Bx9DOZ9G5U%#T#_w^z z>&U+n1v?f%nEQ`<)f}1_d-f|9fwoG{YZk%fXBVfeP?)q=essIBAIObjxh%ypm+EEk z?pT0HPeF1v$pM*$q``5pJ@%7gn7ANYXRxmv{jfVm%V>Rx#NfZfj5K;DNM!75eL8M^FB8d<8os$=i?e#mM)F$ULkjo|N zT(KP5eFWl#X8;`=koT3hd*N9CUoAlJ?&norOmWfQ`kDOB>*3cPd*lDtj`vGPQ_+iY zIhY4bR{!F9+$bwylWze=(S?UpqUM1u2xxSuDz11z*=Qo?B28;TpF*Be@13G^ww9b0 zg=uejJ+R-;ouhT4&FCp?Pkj`E6MCyQh!v_V9Kn~dv(kZAPgeJ>>lDAQ2g6njk|AHo zNrr}5Rsn6&cLfF(!0we5z-;;*3oyCg8Ml5Tm%6e4%SK@1|0N=Sb)8CzF%Otm&T62; zzheQ$r&+NwjKB*s=VT6$@K`J<6xvF)!JTxfz46k|{13|dq)Z8~J)6mdN8QbPrDRZF zY4ppA{KeV&ek`SR?qvy%C(FcM2)_igztiOJ|yb$gODD!|>iDv);&o8Y11&ofJ z-W%*i^XTw3Aa=TPrTID>)r zPu4s2(_{@qZfvsvzv%kk{Qv7b{^}OvTLOc5z~pY=xRLt)56}PFo!}*vI6d!VC;QQK z1+-0aMjCK-%;{(PN7ve6!lSUQb|={(Ix?@`W0|n5_r8~IDP5(hw)q3$XG3t1181@sxSMyiisF2G0Wweg`z6ViE<|UiaDjJ1olq=9+&9YSNE9 zX%jqF_Knhn@+i_5YvvSB?dXyvVK13g4EP=0=Ey7dp>*bAmEbtxULKsiJ`DW7x97Hf z(qD)K9E(KSYnAcq-e;8b8V{3{uzyOUo(ZU9w0)3=Nj^CfClVR=&KzKt)%#@J2l4Ih z-!HHL8~@+p@prU&{bnd@fO4yR|2LX{tC>GOGImy+9R!L6P6azKn_h?AN)B6ttwWaf zHk$ujs?H7*LI?a0*5-1j%-`qUy3e5BK(j5@k{kuyfgcz6Ph^tT*v*|%uULo4H~D-Q zUGly3aTJ!7{>WC;|0s{~;GG8LSqUwI#6Tv)Z#*~a%#;mo1?mM#2h_+x*;~_O@NT35RoG>O4JkhW!XKW{Pv{%2c&LGOfk*(ZIJxE(!MrCcVqAo2Tkw8_~S z{i~0y#7D>eqvHX6%!jC>tY<5nT`o~a>}W)|Coqoxfr3lN1O6RW1O6wR%}Vtr3QT* zM`%gXphgyjOSwo8Mf2s44ny?}W=-}UPMh@bthJ*SQTUpwAEdbGIAYrl2)w>!uGE9-Pg5-1); zZjoBl?q$sxTj-dPwL+`GwL?2{@IJ8sS(bn{?X9;JFzNzX=a|Rd!8we* zGSdm(F>8Uh^Xf-OY?J#hBe3!RUqzs=lNujdefUst6ngeFoc-tR#m4leW|Qfb~2K7^WV1 z<-VQn4gHFiU^|yQShH`ipIC(10Qt#?{SDvm&AqXD7x;07Q^POYBE@cVHCDb15-UkF zS(3iC1af)^9dzLA&7Zy`usrH^@8EgBl|@Pv?leNmc@kXCLg7-sbi!z{dZ(jlh4S^Zk2G ziEHJFnDYO*i=FHT+-uR!EPl4vwYC6rFOVLcMpI}oUTxNN4_cC!(^eEJKGvG5;qj{bW*0cSD>wsz@IW3i+!~(%w zAg~6)%bNP2RY`-o;5@RN+fcDRQ{_lT<)e$!T|7*ti=Z|6%SA%B& z^iJ*#@2^ea!~)bS@WcYi6aOD8{|URst0;=5_n6OSy=tmFx|hRQwuKnfRKEHeGFFuD z4DGSx;^=pL{p{mIJhP1f|BCMyERvN~pqD(%p5@DZ}Jqz$I7I5SL=gIt4J?7=SiT(v3*;)C1D3oOs zk)Qcj=$y<33~F*^c&vG+T(xuM-_~+*R%yVQewG>JD%oLjq=0|tC|*aaSe);JLK=3n*-lg9)h@Dh6?0yn6M zPDWkj6PhM;7EO~^QfDM*Uu9oY$rg!|vbF^MmxOG3!hXWL@|}Hy_keya>#!5@iWY_7 zfMI2;`1>j;p5I}AHur;?$_*%&$ASE83o+?44@PU%2ld%!kY!)U$-$Us2=@Y${|fL= zaiiP!g5SXcZv1~-{_|p94^{x{|Hu9Gf6bX~ z{QsJ9{x3%<{)KPF`d#b{{u}}Y9tAc6Pdp$tsS);$O-SktdL7yWf=0`X&-@2HjMhp< zAM2LtG3asHSKsE3dV|5>>4V%m`DD&-&>wr`p*CUMn|fK-^9Hq*Vk= z6XvUIkF(2;+{7YOX>t~V)e7EG@;(Rioyl_mS%wN-Iif{)& zcpiSN{O4X_-?0O+7!)g1Dx4p;v-uYoa?*sN=#+3ZIwfUq(v9|9oHA0&AQGthJjg>* zH(F*uf7Ww2_pvvQddmA@z;HtDXuf@2zhe*Q6P~3bfQgJZ|GIj7g>iin0VvcvQq`-p zOwJJz=E^x*WavbBUA2L3mDY!3N06|7;5p*aOGoN6RUs&z79cydg7@7(@s9Fgbd{ zzZ2!mddD3B4AubblV0b>{~M3M#{Wm=e_m;=|KH1A@V^2NUUcvEJm<$pJ66FUTd@X3 z4(v4l$E`Ktxy*(3mH$}d{o{Tv1OJZ(A_8st16^51+09+-N5vSd0qV{cSy?q$PV$44 zq|s$-_O-XOC97p%D-`|{nM#hlP}avF@799M|YtTI5cYgN& zwpxH=y+gPX0ZinLS4mE4w0i~RO5XG*`kW-ZvuOSe%M;E2ypbz=a{@m074Y6?xB36~ z9^wB*e_X|x7 z!oTkX#s^8DU5WT(=S^n9_{2vGyd>>PyODyG0>L&Y^@jIG8v19aXzGJ})df~yw*7GG zC;VsA7j+E!QF9oq0>oaBRv4``Th<@O(Qd$hlB{F=Xx0_FkFN>cqy7EtzDAiGt<@6U zGgkf;%MzSZU{^-Jm*9RwvIgn=CP>`+_I=b9QR&cpLU~cL=Oqs0^0$x$iVXCHQ-F(-6z}g#9IPp|^8TH?HRgdmo_sX_fy`{W6~eV6f!UOoOr3#J66y{92PsMPZQG#i zkLP9W|BV%hWCdU_0|@Q_#IYcW2XYDThJ;ZXY#sUy@(kabv<8uP;CIK~ zVcQnJ*S*m{;8c0V%4|YnjHa5Rsi%`#<-B4@~a9NwEMCbC=D2 z!nl*P;$_lYeR_}=>Kxi9@q#YV{8z0JISgL8jQ0AI7Atz`P`hncFjf z8~<-Q0`JN2|DroD@<#5meh25?p+JfHgPDLZ-yblnFzRqD`0F@irwOISn6@V$%nSsI z#$J%9wxnKoRmtI39tP4;RKh%(ibu$s`)ms%OVkrU*q5Zun^p8T|0Nr|N{8d`ZDo4G z_pxM6zf&*Gf5LbibZu8^6OE;3pj**YDL9+K`Q#GKzjFqotYEDGZvPDQ#@GJl|Nj?8 z->x5fa5WDXR?4 zvuQT$fg(?@)tUr0!VgK0szrV;%ryq`^N`R!NLsYwy$x;?mIHRB*;k03F zi51A&?+u10FWozH0)wqOllCe%mTU&r=3HlPt57-un_GVvUSDL$^oQF~K z1T+t*pOlij3_-ayEm54~x1$Lj&A2wv-oAf!OVK=$@Rj<6a>W)TRw0q_-X>=-9;{=6 zNsae;2o?tYoL_J@wU>i8|Ffc;=3j~b?N+zn1K#+5(-C+}Zr@6mN?s=a{+;FB?{YWr z;)?H{J@e1D21<-Pu?Gq#Suw#r+d3GMhFwrzm2xleq;MB7Y!K&c4c?B_NwHnJc$H)v zWEsQ{$$Ep|%VKbBkE44EL?4qh|B8-muBC~tHtSr~&b4Q>gi|J0d_OpHf@L6wUhVTq z&d@kI?JNJ2SVuSu<2)wc037@dkX*~Q0{Dj4|K|VSirKf+<1()0PPpC^?Wx{Y`USQJY|Gn#Q#@I)kWJQ=bW7PVUVAF zwv*^XuzJE=N6QZJZvb%{H+n5s`i9s4_{Vi^{C~tNz{GC+&N}YRy8-*MGrYU(eJ6Nn zhm!&S#h4Ydoy%%jVj0vZR$}g@?h_!1#6i1X;O7+vrPDk}X?PgOIXdh?O9NJ8GLIO$ z*we}?zQOCjYu8?zacO>$Z<>u-X**FeYYA%-@EuKgVhgIbalF}fA-mqua7@1PADpiq z68b0hOZK_)FL&eCZ~Qv1@*ijREw9*(|F;~?x6-BJH}lSS&ToIsyJEn1fmavofHupu zm1bAl64*n)OqAEegC?y`O8NvfN^<08Ct2B_Z5a}OtFSiF7Y8e!i_Nf#x%YZV_}6B- zat8GidbR7Qe?Z5>vAt^j^BsT1*Uy8)_(XnirrB}|?W3(i^w#=Zlx;Ns&N&5V8}Oe* zFv@$M`~uPBcYw#O-3<8GZ4L0vEa1lfTQU2#dh}-88Q*%FSO8&eAI$Cr{$r;wuB2ET z&c|W2haWJUOTv!8+Tc}5pi0{o49J&eP2jR#gZe<%s03%e3~a>2)q3CY*R~S$BvoI7|LRvd(0HI%vp?8tr^(qx^FMe`d6>Mv zgnl6fPdzAi|0m!7V^ps7+P7Z<-S~gA5qMi}-%8uO8vOE)-VLt&*b8*)VnqM3OQQ3v zc%7{ft=E3Qap0}Bed}O5;HpV#zaKCq&_CfUoBX6l?j6PkD}70f%?q0{+vhse`F@~J zpf~Z|3CR;`ttnROqeFG9FjBf);Opu30fY4^K^7Y zTY|y6v8I1k25JSa6~=>D!AXWSsJd*6bL=S(gER0xvq>)7ngsjo z+QGRG&T?|Da*)MW`h$BNgI@rhpX8Ike_s2wZz}+AX8$(+e+S*)Kjsv@)jPjap8u7D z9%7g9IN`$E68J$LCd|m3>j@o&K|72D-y+D(?3}moLG~-F792}X+J`e0W z$tvZX!{FS5vmPCp2o_-O2D5+fXaDk6SpS>X<9c5Cg}xa9-~TNBW{HjekNN2ucK`>! z{ij_03a7D~iyVUI@oeWsF9Z!JoJ$er&A7l;z@?I`d5>ln^+rw5uUO7QswY>1J(_ffITUfcf%^O7I}GQRse;D6i*D*zk+Z+FdZ{J-5;zO5$X+y8o( zq`A{2_H!8rUHm@o$6l6b{-Nuj5H?5?BE8wKQZ%^tD#(Nif}>ukM~Gf_PLG3jH~#+t0`0~&&(PtGrpL=R`3 z^h#>0-Z{3gb$oxeg&LfLa;BrbzreqJ!T;F&{~N_W8~<-h?b~UK3%Q-2g7yD#uXtp~ zC&!Yk(@o~)6MjZ}Rb}tIM)3o0LO$4Ma~Hjzh!bd5`(r+r78n zb+j9;c@X;PqdmkS%MPHe_V#(6KRH^}Aj7LtNkyFx$1{?XPWka5kLZJcj!M63o1gg~ z@b61vouXopj{dXn{VDwW>i^?fu4ChWvJmv%{tE>-6Y@NG_4N>U!nm=Gy23jWb?y+;I(|sN$ z8U!}A`S*wMIi8rk$~QjW=`YQdYG23E$K=dLpNW0809I#6ZOII*$v7B!uQS5_%*8l& zjz)x&&_AGG`mRI}{X0+o6ARtW8z=q`?f~Y6{pFwj+g?krYuo()>tpk~TjnKmUgCcz z0&sYUy>xtRi;x_GqCDnr6kQmEXHcT?~GM|B+b2J1JHJu z-1Qrf=X*a)I92oy`tib6MAbCkhW8(z`8Vq1arP08`=H0*9BNys&cirI=lF`GVNFJ# zt6~Y9BP$VzUTLeZ(&@rKO5Dy3>DTGcKbtsg_gtQy{+Joa6GDt$Xq9OYXg_;Hoo z1<-qgR=?N1;+^M`_hM2kl-}3TRh)MJKPbg#2n5 zczmhj(LUSz3;Ykfe-i5m5lXl{QdS;<^Z$)1B}_f z?qYAm?z#73bP31__}8XV;AtMB`A=HBdCR7MvaJ?rN)hc0(Sk)*g~0IXbO(o}wAO%!UEQ4~Sk_hxTX>u;rer&CFuX8y}j_99Dz69)h z)9)So$LHC9!2e|Re=_^et9hZnotuw+)%^I`ZT$cESp6PJy%^W|Np{I|@Z;W?1tk0r zVt0ccsW~$Y8B7&%+N@7Fwq{-6Je$k`Z$f=SZ8WVxuaYW>B%+whpgnj^^pwWYtQ;n9{MS8bzfCziozo2^M&?x@Sr=(JUuSebzTI1K8t&aEukx+XD+{0D3Q zLi8W3{vG{)@o)V8hiuXRo7umO|KCIPj~`X}A24FyJ7bQn_mfq7-x*$CdLAG(V}`hF zUz<&RK<#MOy!S<8mRGMi!!i%mszqHVfo2jk#-gyV(5amaJ=H4Bzu@V;V*q12(NS{7 zzEYwi&{;i?bqe{TYfG+bEBU&g#QvNMw#zuSV8;f&fAYM)68Vp&Us?NCX8%(E{73w6 ztp3j{xwJRC`hWaW_**46{y+XFYy1;f-p0Md#^^Y}SPAf*?Ui*vXR8lU4B@v2Bqr1) z>>1Pv2^oYP)yj!bl#l;$$90ERB_egc=?+7kKs*$N2$-$?oxt$iK@ZLuEF z$UCL6?k9QeR4wORHvhu8C36s8egEJcX460Fdk?Jr3;+64-e0-kfDpJvDHviQl+7@WX0E*oyNpF3Irg<>(-syY)9d0c}%R$ z=(r?%&3`cK54d-t|H(^v@=reV?|k=H`RxyT!>j*K`0U^J95?>ocYNPU`@9(ZBG}v| z>to8gnC%r-a__8~$&yf)c>f|W+o$^Kxd+5m4V*z=wFh$9T+3ta6&8^wHu1v(l@ob8 zNY%7l(Jc7;l6Ptcx+gW&r_zAzK@v0#&cW#?)CbACJ==Q3Dm`j{2#zFJ0cQrI-0>T$ z?|1m0_rklIJoy(y|ATw~LiF!^^M7pK|4r@R>-_)UZZ1WKu$5F*+g8n#QC}o6M4MYR7{%(T3;uL&++003iYx zP(0Z>@}n8{YHjI3Pa##yu{E0Ws!y!O;K)Y_>-s7Mz2$NC(UmearJv-SeXjJ|ZuYF- z*$R<>Gy9k3-}~9$*(?9{rdR)VWqr-)X-s~e)Gz5i_dn&1`%~$3oNfPOBj6I3aXTvV z-|60p`3Zz~`$>Li6hZ=XC%f|{gMxnfc&*S_Qw zx!Kxc)uqsDeFTH-I>J%PTK8z(LfxP>x^0jzsi0RK%MOC~=Qzt`4b%E*AN?Hr`+dTH z68&30`5R5Y;{AF58~txQ0&mBEas8C-bN-lnUjDJGev>+nfQer^&jJK~-}e)81WOP^ z1j2Tb6#p%7HDEU((c#?K7JuHQ$$UE(?R>Ofj^_4QGa++eNdlSN#}be~DGjs*X&#K8 z@?dE3gOCqivyWvDC3BQ8ju)iSbprbW{f5_1lCTfXGO=6Exep?Lf%y15_fKMgWbHq> z_hfboq0ugD*6 zd^h$={BMvHL;_yy<4+BC4H~8Ck0!CU0f`QKgPc_!{~a)D5Im9AhXI>f{SJw~L4BfU zK)V*dhagT!>)UTE%(2X>GM(Y(c(_@*O*QnZM!oEU(@&xk4=O_FR zUXwJK`}_LjpYp3e^IA9h-^KcUkN-2Pr`&1((zW_h+o|rf|1wH=Ny}FE`Un`a0Oec3 zaqoCdh5vy039ImgZ;gro_O_dQUD@;Ie!yiVg@2@)(h1K_&hXAknvgo_rPRv9K$A85 z(OacH583MgQSJ0LbXWh01e!^))(rCVFxGP@86>bRgJVS5fsXqJ7R;bOn)_Vo&%X1g z{p1hvPJQQhd*FX=*dHsps)BfuoUz1%rPI52hPHo%%$45X#FXxTiDR%+J zeeSIMIY~&PEr2ezlf_|AV9xN}0*wlf4u?gd!0#-6S8hjhYt$-SYp)%|!HEPChJ!67 zv>BAk<7~#YWY^q8LeTANjrHC+MsO6T-s%HzwQj5azk&a{CePF!^H0lPU$bAfzE=9O{%b8;&9_J3XaW3_+>JYDCLrbY55_zo z_tq95shiXqHX&GYVN(c zhpxTAelW*2lEAN%qWP;9$2tV@g7=z%EwG6%&x7`M91Lc&efNOnZ2kxQ8x~+9WiUUH z8YCf2bXQGc1!`NGoTMb9Jvp)-W*ry$a~GfaA9#G}c|c+b5}T&&`-}d`6ThV)lR|C~i8i85B8>SLexx*rF5&|!Mjy-2RQ*~GjT5UhVZV}LbB@W#Thl*T^Vjv6 zpV2da-TvwCUFqNW|2^cs)lp4!wE%WE@1&w?7!zLBb8KG>X0t6q?&mULyi($y9YzOx z%7;n6D6f97Ww1|r2@+b=VhIGco!6wUY62N~@Pom&H|6tSy&p(er_x;V#xamT=wihE z-f1-d)_KDY$SynfclZ_xCG%=d1s%K8HV7&nZg(bKk+gedfk*1m=w}3y_*Q z_gq>kn(q7Rf`|9LuQTr4=i;z9noohvD%nFElBWl}{j}rVXYqcJBgI$`r93{b^xJA1 z9HxW0gsu7lUD?{5zH=%4gIBfd!I#m#-`d;zY6YSr0fByJ-ETDg%DSJj?iW|F(f{rv zu<`$=+Yhb#fgJ_lIq!ly0s|}H!~&Cb0Vf(rX52{$_Tb!z1}aTf3zL|jm{sU>SWfB( z{#_wB*b5w2De?Ccl98sIq-v#pkCcC51kgEQ&Utfz*`<6{}dk$dI~&Wb_R);6&X0@nh`O0xFk6V{a#fJsVTPVJyB zksDYDV+N4qj9x;o<7;A31p6|vLBa98xwp?&Ac_2il|N_hAAH--@%?!(||p{nsqOx&D-UF8{pz`jNJlpB(`exx{B;1H67O{4VbduR3^^R*E#JPV7N;dF&=} z!Q9KD)EfJ-_ca-Q+QrCO43$9*w1C)>j!lS^FdkL_bc?v!)We>Rwt*2#6Adf zklE~yW9>yR(dX;T?-tDv3SLmPEC*?{1@#Kzw-srVo=r1JT zZASi2)$$tu$NHyt?4R@e`a3>KK9_%9e!c(J@?S^5Bq|QL9@qf;6wC%D>jB#Hfr;-= zUWO%@We->loJ-*AbF~28kv}!m8?nK_MhG+aXiK4O2?R?p7l-~yS(yQ4X4>Olf!LNMijb7SD|1TSX*ZHq&@F~eZ%I`_;wEt4e zS!?CC^!6iQVi$U;H+DPw=^)3ydd~;ExtqN<7+3*ozH^=LCzilreo$5zu2Pa5>)=ER z3e}^%y0J}a1gS|b+YSWu%OQ{pxG(Bu-Oi5v4f=yL;J%XK`J>NG`wE;)^5BG8&Ah^P{lcsN?MMDk(|BU~`FS5{{d!w+XOh3{^=19J^i<2Y`hTfyEB!Ac zppwi4=C!X;z+^7q5GNgV%R29#cX^-i9V`FRKD&4wtkFsP?_$}T|FQ^`*u#G%At`ns z_eN~s%Y=9(+KK0n^Z(NK?f5_Qemakr zx&K=3B){$7b_8lBU~3c*L;>0sz@TulE|8R}Mu`Oy!pBPPg!h11gJFSnhj?$kZ3$$m zZV~|masTLfLAI2$Rbq#vx3ShZ(JPQ&xggqbB7TmA4Kj{fX%d?#&-%%2^6Nh1A~w(e z%SYhb{2#B!mpMMRowp}VwLjH9%~##?@=0#nzuySV3%iCe)&lUfP&XjHl97V4mnC3T zaFPaT?i}8;@6Y*8dqyz!&P-npW681wq`(4r^KW>4L%z7Ll;|*Ged}Y4W`Dqcm1p~X z{Y<~`+rE=u_1Eo}{eC(5$1Cnc?fYGSo95p;0?JGvm{kaSL`)CUw)J&p)dO1;kH2X@qYK*!;UDamWj?r7g=NIqJYplsv`VD*IL;%v? z@w0l$pYQkkZ@T(_?eEHy2+oUD&@RO8X>y%1b_CfiWoR~k+#g7=vV$fKzJ@d`Be>O+|0Y36@ft2TL8sRd&lg>uR`#I>q&kVMx6{}Z%t{{K(-l-}kX zm05sY>rJi}z=#2ql%JgVe-a5;^Y6p}NrGJ{w$&0$Y=E`jpF{@6>VI(e&xr_}G+H*1 z4@yQFye9F4LVuB8$=dhpan-N>&2#&0uitF_HvZp+tv^GHaX)Z;wg3+Kv04D@Rlr=W zz{w6s^IS9+;l=pmWyLsagt7$oAV2e-&A#IEC+i6G#hxU3i~K<<`Uk#$^t?Z=bX)cR z&Gc{a|DVC_Tb-p`=#^ZLYrm1(QFDNzzKHBQgVytN=Wak&J+{E_b@k&xJTNI)MFZpq zmLX|#$^z-ptpfRh&yP;dtUuA@&A+eoJ8S;Ivp&A9`u}$NH~!y>tUp7~6uHuCyN+H7 z41O=b>s~zbfP7*B0{*MsU{{*#=#_zDd{R~-`hkD1Qc@m0YcRIaX;5QGN%J3AlClBX zcl;dRU-av?>h}TY-}wJCxP7a$ocvmtmjAz=SODQpz~C-`F$akLmVmaWo_ED=!fp9% zUQM&YcaiQzKXs#7ciM{l<9@8Rpe#gm8uTyrv+g?L`L*evAL817t5N-v^=$M1Kl#)9 z*>ey6H$ck!lk(cI08udj{Ey~eAl32yC!%N5o6uesp|;hc_oFR8 z$%!E*u?6;}?mO~NUVd#n>HD+iy~)4V2yFbn*T{ZMy>^+b0On^4FmHl&04D_&V6w$i zV*#{Tm(`C?EJEc4{Fv%@xC5vi{RdG1Okx2> zuLMZX0s{U=TY#dt7&a_Hk)O@Bv$yv72K{9Zj&?v`f3{3?1fV_RpUf|exZiFj^v})n ze>m&6@&9&g{TZ6(r8WO~EyU!LrvbDrfWxYE6j1yp&jpqR$j|En(flhhy^$;KC;MpX zlkJiJq-G!!Sd;80{0HxDSAz3;K$et4%*)AL4`1%v|LL_kFe{|GP^bfw*9~?n$#uoX1*sE;w|Kr1c zHt%s}$}fZYmG}H#pnj|tz{$<^+d-2!K(GKw>~C!WCe|SPn*!O=jVb(v9h>j}u-Dke|9epOk##EH1f1LhaAyAlMtzL}3~Mku21uR@sDuw^ zCJ?Ycp+6`Q*RQjCXJ4segxZ GbJLEJCvnx9<97WaQx`o9(bpDaV|U%Q^a)%IGS zuj|kH&$+Lqs^)Eu_pm42(${M8zqW(A|y z3!N5${NOIX;`yU}e_lIJ`RWJ0`v0BJ{Iz#_qWrb?s(+2jWBqHb-_rPH+eyRs=zgo? zeR=eMv1Au=HLr}-e~16X?>lL9N@fA$3kx97?IeTnWNu(xHs<*9X|`m*y?rL%ne!*s zMXr2f|AUXf_xV53Ii9_fbNxB@T;@m0r{1+c;3WUiWyt^!;QfA_CkkKo+Q-)O z_MCevf7Wq4^7Hc3^EtOY)m0Lw@=4;ff7*M?zcd2M(|}H-K8XVadXt&J{LFvc2r+%7 zvIAua23zZNte4x__M*sl*q^NS8&CNAM*kl{|H)&1o0ykd&$Z{=`24Pqb?5ClcjEQD zf8Og{ewsTU<&-Pybe1^h&$)B!mV0RgOnm)CfPh?G=K!%r0)Z_M=r77EDdbnF%DGyJ z(cCNKCw3&SjAwl1LO)*zFCBHO`#2-;ZU28f&zE^R*Pe6d_jRm0Z_l|C_pkRlYk8VG z%b#;c`*k|YQ~hcGwD*>OZ3OhfFa1jUexNXSw@&`q7G$%35c&qq5;e(!k4_LKZ+|6K1@{;?5I?g%({1s$R* zjWq?9psav)61Y!ViuxqQRbI;F+`RrDYWcooXnf~g{A$L2=6F9N`@Q%1Z?-%C7butb z@+-Y^$cAlV0wuODdtf9b3A219)}I%75f|ek#lCaZ{F|fsXT0m5H+V5%nUi*JN?$&P`fq!!Zj(oqy@$>p`m!G%( z?1&!wzpmwb?LT|GO@14Je|-dgj{oPQ^(CucmP+>Pn&%qM<+JQLcW!;E>*e;RvRi%| zfo%kSWCVVO|F5I(rzD;MSxe>!sQtM%OboO{~qTzk$vZQt_S2y7$pvmv!r|nyQ8-Z;Ees%;l{(pAFHu-G?{++M zj*LB}?!Rbxt8LNYZFSoSY$Nb@kHE(N-~IV+ z?`|7`Z;iml|F=eGYuiTP?;e4T|G)e5-QL|c0^b^e*Z69u^c_5Jf7-U?|HctGUh(J8{q!D>HBYyvmY>-_)qBdH_Gdlknxj29 zw>-_ArJmOQm;6!FS>LIAsyXfdi|&uz=k#fx-{Gm|c;2Vdx&33mpMU%8PM_9!6sqQ2 z`jOn%I*z@LdY*2dYdiJ))-j)+&Fk_fsnh=Xk)EE>%WYNry8h>Vp1-S?%g=lL4dvHI z_&f%$%lr)^@Hf7P^OgVe@9xz1dC$5Ep1z*9|5*8@Yw3C2seIbMrulpAul0Lgf7Wx% zoyw>ETg)_`AM>M>&+qa189g0~*QK8K+RDFw1Wp9MwEzCuoqByOdAbTt`>!RwU31d> zy#Kj;lsIWQ%bz7qZMFYi`%~GM^IsnCr1LC$I>Nc;Z_7O&*>m}rJC#rSzwQ11o$u%L z*`2Q6+W+V~J@xrf`AzOm9)Yi~i!VR_bNe~>C%@nA+`l&hUw;1Iliu355!goHZyJG( z|G(+e+}_JJ0zWqb8~;ByUYqnb0)Nv8Z2bRCpXT;nwh{Qb5!m?ux$)Yhw-NZ8MquOr zZ~8R1_p*(^&yB#w|IdxrCcTZo-!uXn|9{h`xxJTd1b%J=HvWHZyf*1=1pcNGIPt&j Ww-MMzU>kvL1hx^_M&REXf&T{~p9cc~ literal 0 HcmV?d00001 diff --git a/Source/Anonymizer/DICOMAnonymizer.Tests/TestData/CT1_J2KI b/Source/Anonymizer/DICOMAnonymizer.Tests/TestData/CT1_J2KI new file mode 100644 index 0000000000000000000000000000000000000000..a8ca94f11ddf4bbe8297580d4898fac85f06cb3b GIT binary patch literal 14156 zcmeHucT`kMv+o|_fMh`d2}6#KGYlCdIONO>h#)zLAR>YU2@(|q6ciN%0m(TZauNY0 zh$x66ISGPd07-)IdPa_)-#OpC_uaqVT5p}rV(njdS65Y6SJkd=77+CR_EFQu>XQH! zO!dg1N8n8Y!63sEgg1C#B;akT%?_cF5=dD|6cUX>p=6O#NHkIs{DNLEP6{ZHfC7V* z-tU)^la?S36G$*f2^2~aB`YC~mX$(D$swgArDRAT1~BW;U-%NFNFwFXBoGrAW%@f$ z2@>cagx8{m)UYOohI&971POEqQdNgTSUpn%ZFMvP4v|AxJT;_^H?+~lApi*+0;Bjt zkeWJ9`=q*>r4deBUlnI=sd`FVRS(cJL29aS=uai#K!S-r1q75uqa@%E^MBR^0|#>d zd&|HfRzUH;wG13W{6!xgVjC?dCxep2fYtE+rOo%3HtJ9PfwAEb`ezF*AtMP|5`VVj zFw$@cv){siO@%{7z_Jjql`XIq34}F)LmrTxArqvjj?h;JZUT&jH^-Z(>*L{22ysM5 zLl*&Tsi|$CiNLBGn5g66P&{O0K;Qx+!D~@KN5RgcfQ=a#@LFIOOi*YWLo8SWib2ni z3~~}9>{mR%a0sWRYJ@jb13SscgE!%abd1zB5nxkL2p3OJCm$zIFBf|^Cxo!u1uw75 zA_!?YdBR*K9=tIG?SW@^51Jyp!8#cKr+qO66#vb>05|-*$ErXc8Gn}r!3e8FOQ10l zU{xfbMFxCRA81Af(=!D7@9uEnyoaZgmzR?x0=yla900!Hxw}U@MF_Cnz486`zqjEK z%kBSC8X5zv`gdtekRHJ{2<*N@mkaKQzsjKkai?cU1NKxOZ=sIV^fBOpXG0(^38Qp`9C6ZA9eDpSLMCAak%h&BRjTU|on0SzBP)ms0)MG}{;VK6 z)PQWX1c5&4FJtkbO$|xP{;3TsgaDQ$*nt&NAtLRQtUx&e6$T@Vl$JqDqfkH_(8BaX|QTGh@MCx zCnbfH0jkT&vO$c*{UHIf5AN?28^rtzvHU`ZexbueM3})!v@bz3HliFEz!fbk1LUy( zv^*F&0!C<{qaaq1XgL`1j);XR2-yBN8doA0 zGSGXDh)hV?AP*uUSo7j98JBbHyR)Z_g}4Izx4b2Hxfat9Uus?pY8Al2uKxJlMXs6BLhCFk|=46Bw9)e0YNHy zCS3o+H=gJB9D`HWQw2GO2jmq(4kv+lAoBfh3=c#}{KkkJtFhsMSQt#fIGr^Q^fOBk zC|Q3|vJojI&a{a0Kr%#53}9RbWC#en@oSWlI7b}F1C&He zkS(yE8wm*HynT%X2rK)A*@&2+s{qJ(gzvG?@3IJm8jvMKkOk(z038_Lmn#Hu5PR;;)0U~VD~C1S)c3e>(NDPj)^paXdceEYQ#LSXL+whK6${8$J&LBtpg zrcDUK?&Bk>LXa8}pE;8VXw-=q2mo9(e#4skIHXkw(jsEf>yqt4koHeZg~1AeJtWBb z$w&7$8a*QJ$m9m}`oGZ_5%IHUfk3V?5wAPH2N+~OpuHZ@9#2H70Ga$+k?B4X1|CT8 zGZ-Wcff@0H`4t zfrbI_g8f1yL_`nzNQsCb2>~PcK5A1KXsLz=)ipSwNP_uz&?4juAeV5*n&S41EkL=DeQs1d;d6os+`r&1K* zWI@P-`xGE{02gbZ2Z$V?G5CxD-bX+$HNhSCA1onC03BiMXN(8~^s|DH*w3=q{Wu{^ z!GiklcUxj1o?r6`RRj(2AHpg>k}Q*aBEbS1nu0#y4#epE$pKW^;Dm7uBxreHFJT74 z+1!Z08=R%U>-Ulz)R`zLfBEBqgqi=_2MdW1c{9U_Ghx_%95cxqaFUz<-*Wng{vQkc zV}XAx@Q(%lvA{nT`2WQMSa733s69Zn`hQ(}V8NXS9^5?;YBj=x1^0t$cxF(y5)aOV z5=;*H^Rfwn1?L7r1@FTwfyM5h9Kyrh)v@5@@UI#Y+-m%)C)J3UaE`%(lLMjuC)OZh za0vza!CeRFC)^8T!O7uYG}=V`Q>UM|ATU7v^A_Z1uimdp?dPfCXRiTKN45tds#C!X z#J`gecoXiP3FCyf2Pe3XHrz7?g)n#swIbp7&*v{~SR4$thl76BeB@-nN_&;=h!p&&n+<>;yFEux9>a9)&e=J7AC*8#?8QUGGL>T&g6$== zZJjk@E0`X7gB8VhVN-zzsheI9#zSG$9LRFvhZvL&QAZl(y?s~Mn-SZ zC8^~pPo_f}mNMcTSH~y&qSi`ewg5${lxYKi$4|dc&x{2hU=r}m?a9)l$pPh2P z(@|-+u^R|3DNS=W70yT7OZoJbr6Jp*A<^$&OU;{x7iWABHr=iHD9dU6M69Mnd}ZjU z&G#Dt(U+{x435cdMD(TPxZ^ZVBjV4G(y4rw6|uN>;yeB`FI^lK9$RAnP;goLZEEcq zlOnR3QiHdOT~eVpMW-$w)6IUOC(_r!`t2rMpCbYl-IFG;r%jfUnyr49Wl^F~>4b4K zwa6Q@;^icYKZdfc^3<{;Qsd%2c?+F$Ts_KPUlk&2IN$PxH~vevS*a~Liz@hfgsbO# zaL$II_3MQz_q>-5I(=o!+SVv#mA*li{VHpICKRvLH+l2T$t%z2_$170%>u-nTqv_I zDw9ewGQyjf1rOX@Z|YaJ)L!o?aPPesPN?%n-pR3{pK8`jT#K+`Jyx6^&sCH0jBJ(9SgUVlvt7f7H8c9>MJ5Qxc`4H_Dr@kKunD$+ei{Zasd|3F&;!3@?^()e=4^8_Rv*7*ba0 zuvmj3or2$*c^^37UDu^u4oJExZde8PV^1 z+q%qWgo}(yz!;JE2<6jKjU*8(Q&`;yxlmcFLmQ0s!z*ov~yUmZiCLK#;K1_22I0`+r+kqQP*}bhg)RTh1+a!Y~g~gXjXiJeIiziY>IQJcH^R zj&+i`z-|w-w^R>_r}x&GyeW;bF1oBbir0F*XFh5yo$+2dQcLUn zan3fFn3}rKY|b*R@!h;IvGA~Sq?sdvj;|S8H2YXC*6l?pt4(na@0&A>|zJ z*q=2>!cJL!iZW|8q2rEIV>q)RZttR5XJpX_ukmJF&A|$Rhf>*LB{j#Rc01>kW@!2m zM(vy1-;`cdMbivT9N&`WaNA@tv$9VSAJuhv@Tm6{oPtE;@lbjbuA!0jwnBEQiQ0%r zeomJHZEm!v`%vdf| z|N7<+iF^vOW3?Hrvf>xF88p_bWai^;73TPmwdsyfbF#=;o~t;nPcz%0#x8C^bL@b6 zN+UYmG2&jOc*R1q=Es8?EgmP6`7>hTcg%BVM>=h`NJSb`&QsH?V}RU3v;zT^sxSg8}!_*NZyRrnONqF#NUn^y`T4`mfgekyc1@ z!w09h8ihKOXO-y>qAR?6FxTpB-4NM!=~JJRYW;+xJKUzJ;!ry}&d+zejIU)BzfBA& zZ+HftWYbGQ*iUB0mfUE3ajC+_WnT zYsQ)t?zy4m4X;ur8}n@S>ps7+{@RsY{@{J(qD(rUfw3Mbm*$iprBsKS6eluN?f``I z8jhdT;oa#^3_M`&M}rdav@wY{I;(lRwewc0=ZGku-gTjLR!^1y!OJ0s4`AA+y*Il< z%d55I3w1@<$u&*d|%Lehe>kh>^%(0sZ+WBXO79qbmqicoAyHloz z&Dk{jn3%p&+IwxLiC)Y8a%GYRRT{9k?H>Ly>n7Zui8A2niI$Q^AN_jT`@+|18kg_6 z=b*has&vn*6)V`P7l~n)g>DEcfYm$+^?cuPbTldGUJ%3+gCTqOc_?EFoZNdqr zm=j0OQ7aTOe5<0Oupy!CsmOy?QdNsyOrEXOO}MVBhfjiGmIg}Tc&+u1ECJ%0sZZ%G zo=Q_aCm%R-Sy0>B*0PxULEn8`mHl+@g#JyTf*sEm;}En&n-b0G5srkBJlw~JD_)IH zUib>O+E)Fz%&AG^|8aL~X=N(qqY{;Cr$X;tbf8xK4;SPL$AzwwX`XIxOXsVs-_Z-_ z4keu$Pj3kFuqnL}<{zCL>Hge?zp{V>yBS`xCJ?A;Be@~bwE(9&z&>kG856V6O7+3w zwo00-6kj0!P=uIe4_5Zox5jaYue;CetMy`dFP_<>L3bKZ2*3UI)#lov!?ve3p1pkW z(CpG}`SyDiPGQY=6P^l`-}FclKETB(`OJXD{yEc#*>-R>@8gW4>~|0qGy0|QBThTc z1EugAhMxjzKYkGYB<+%XO?mW)XU{8kyM(xlYOADoA>UUPUpFado+zj0u=+f`$m@$B z&C^kKd0!B9<5hZgi(+7J`9iLG)0cDama{#oBnHa`J+s)NkJjPnTcz}B>Kr(!O=j&6Wi)8{YA@{HAA?7qsz=#rRRLD|4&Ts$_n z%XcYlPW7$wMH<#%M!UciPPq9ArT)dKx3!CT7Yp^0hhoGQyp29VBSTMb@C*o}729Uf zv}aE^2|F1akIod*oA977sa(#e3DT8JUTX54acSqU5P`8=SbX{2Telh+j=&f}2i=wE!#c0KBm+so=z=L20g zq7Br(WJjDUkQ-Nz5$Mr~_UKUAG&!6{Dw*%OhFA7VtPvD6-gx=B$ooie$n?$aJ}xBf zOj+;YnPOc{MYuyxvp;w9a%iIa_(;(7P1*xxo!tdk$+=@(WAB{WOc`indZ0vUk5TpN zW~=7JITi1-Pq!c6SB-)j?NWYSxyaS>ka($O~OmeSc8*4!c{{JZFb7VR2)+EHf; z>aV6N=0BQh(dXOx*0A$h8&-{91t6O zNPXK+i@UxN#(FhXMUPHwr*tbnzI-m&bL8-?dQbY|3ZKc&c+Gez5x$8BX62HD-Tw*Ia|Dj^-k!{rBM(der%gkZB#?!}ZM9egoT7GC69HOQlw+(USOh>dAp%J_3 zY$bCu!P$4}l^D9Jd^SrY2Kn7-I1O)!_$qimsAxJFR4Q)#phQ(Bi5ZqGS+C>fA8zbn z!hUr!sCc_k_)uwP2K;T$>YHU)yizxw*0Mw~%)n#B4#)9Jm0EUTe-5eEQWc zp6QyGOs&@$`MXxS+}(#UD zywy%{&_P@ZqmJV}%prNJB8dTb{e)of|^}tBo>ZRD9+7YT%BCAEU_uK5nnWFJ{)`$j+J7jugi+)jhkOA0e^m zOGzERGcESDDzO;Xbn(cQ8+`RI-J`B6T$P<#Up(}ctv@vPn})-^;t(y>&A85^v3{i$ zGFzIk^Y zp6J0-)4W_XzU$v4c+5i5*e9=ST$B0gbw{e%%Fp;nHdySOwfa)4XVBK^yiu3ah41E? zHe_Lvs^SmwwAUo+B59rChT6H`Jl}I3cAtGWap7^6`)S333aLXU?85jYABW$8*=yu; z!+6Q)xTi`r$q%G2Kj)^7K45$A+4JjFexr9fZ!ck-_~?4Re;k0$tp}bx6Y7!bMIm2w z)5yn9d!jCTgGuW85YHZkq6ClgN;w0S1!tNLg-rg*?r7?(%p~HESUu++eL9{|tCg$n z7@+a)(oWgK;Bz*lFJHUo`cugYX2dKok$(`&w5E>8kf7=mmYyH2MpZrOat`~$;=(&} zvLk9u9?>SJN|aL_^TA)yudAo0xth_e=$N8y^%O(t-Y{zADqEMuyk9?Q73eR$9U{Xl zv7$!8b3dDQiBU#3J)?hwG8JP-DBxUvsh4ricJQF^+=D!aB& zEuFf|N?hmW(up@lZ$lJfa~BjX#AXxBC6mcq9O^zLH}>7WD=ObQOw(Y}T{iOU$bAx) z=0Et{Tj?Pw2DIGJ!G-xcrCLs|Tn%pY{F>(yr%z{x@Km;BWzw8+hFdUouA%XTLmXE2 zdgJt5eM*tiHm3?KgrIoeM57(1VGfaZIxB*~y~oScjEYNfpRlRiGm*~z%k&f>;U>JC z8}|p!Ze?S<6k2=F`&uc8=GK3mI;{UAp6%I|@_T&b?Ky^IIyhCWQ5t?*>Cq`>%Xy9o zedNSAA3l^0LuQW|WMS zu+8_AbD76xvur5h0;Ofz?E>Y^NOoiU+(ZlWGKz0@ z-kB*m|Gg^=h8?Vrr^p0H=5l3;?V{tEJse}P4L+L2RTs3{1w(^F4Rw<11Go&Bp%E0b zI1_V#e1?_1kj3}027``GW**_KEMo+Fexp>EO2ylPkEOQhp`zWijsDAXidt@JlK0X` zd^@elOon}Led%*G7kHvOf2k@oGArn=W#raV)$t*N;=4spNt1r=uE9F-Ih3V4Ib}DB1&bAxUQz@!VX}AK5|mudgl2QzuN_p9 z9rtV;@G`ag=EG&WdbTNFmY!GZ>MmKIj{JcMwF31EWR-EcFQP)$=oWEp5_2;pO6|Pt z>i7Pr-L$|^@~|5nF5P(RJF}+YsF;q)D)pweq8V_NK~Gkfp?LIo>IS!AGuTLNbh-F~ zu2oA``|U`7AtAr4^=Ow%Hhmvu&AxbG6IzyBMIA4HPA=oMV6R@AEq?I_o60NJ+uL%s zK_!v(oU5-7tvzhaIH~?vfjVB_Xw06MzYPABpX@P>1+!Hedi`xJ^ukJUaWE|(ezP)- zy&>m~cF-M?-Bt=Ix}akhK~n2Pt#& ztWCy7uKFh`S9HCGxP=BLG^Nz4!c>sr^%h$fpz^@OE-shXYrK%MHAI^9)KjE05o2q>2i*(6Zv7OJwiG za`Y~Eyjgx?oPhq4;KFYf8goVO?EA~DI*A61#sa%i2hxOuR;#K`eSYe@%QmvTQhJ>u zKey>ZE(+fIIqPf;RkU4}(e-q_&}LlN7)g0`HGYLA=f-dVoA>GTE4!tXR}Hmo&z8EG z)bvygKYlNx@|J7%0p&#P<#MD>7Fo~z_4cJ#N35^$y(C*wPVbde6@BX1P4CLEdAlyn z7V}5G-RFp0`38;jP_u3a0h@8LL;SDpYrkXjF!!!5-k%eh4&_!XaVV5d~A08-HlM2fO5AXv-Z@j zRsPP;=>@$A1*G>a`WPpF>9!lYS-KrUFJuQw>)kI~%^q4@=nDJ6`IR~y@l*~%E>u71 z(mb-I6{blyOPSIH6*@MVj(>G%k1h8Fm(#lF;qdeio!370rEDs(N~?T3iFL+2i<4Zu z>(8L%wBSEl+v#$P<*H25mtl!R9vs6Lc^JR&z)a}A^ji)k#_*gTz5M!{q^WuN#>2{; zz&8W!Zj^pJgK1Y~_TMP z^4+*%GircN2C3Ue+LWDv3<{oHO30@f5oLAiTcZa{-DAnzMwtkSR0{hufC$DA&O0FYO}~EF9kZItrbSv_ypV)v_*SP z6w#zsbfv(zy%#yU3GWky)k>eEfW7xxN_)!TIji+uQRd3TsGrwJ-iIhI(H zhnu}Wb)edKAUe~q!J%dvKGMKhQ9;v0o5^6bt7gb?mO0^>cYZu6>ecy-AvWFcEewEgQWN(u5BlF?x579G)1y zL7_e>P!jtsz3x-x2O&J$w7A!Xd*IfsM|a=SJ@XKY?O7Kz5NPSvK2>(14a6xXJg=N!I=R>jU7jw?fCqApLoJddFd;8~q(fl9VFn;_1 literal 0 HcmV?d00001 diff --git a/Source/Anonymizer/DICOMAnonymizer/AnonymizeEngine.cs b/Source/Anonymizer/DICOMAnonymizer/AnonymizeEngine.cs new file mode 100644 index 0000000..cf8808b --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer/AnonymizeEngine.cs @@ -0,0 +1,499 @@ +using Dicom; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +using AnonFunc = System.Func, Dicom.DicomItem, Dicom.DicomItem>; + +namespace DICOMAnonymizer +{ + public class AnonymizeEngine + { + /// + /// The engine operates in 3 different modes: + /// Blank: Introduces a new empty dataset to TagHandlers. + /// TagHandlers should explicitly add needed attributes to the empty dataset. + /// This method accommodates explicit whitelist scenarios. + /// Clone: Clones the input to a new object, which is then provided to the TagHandler. + /// InPlace: Anonymizes the input on the spot. + /// + public enum Mode { inplace, blank, clone } + + private static readonly char[] _parenthesis = new[] { '(', ')' }; + private readonly Dictionary _regexFuncs = new Dictionary(); + private readonly Dictionary _tagFuncs = new Dictionary(); + private readonly List _tagHandlers = new List(); + private List _postprocesses { get; set; } = new List(); + + // TODO: A way to make descriptions optional during testing + private readonly bool _testingmode = false; + private AnonymizeEngine(Mode m, bool testing) + { + _mode = m; + _testingmode = testing; + } + + public AnonymizeEngine(Mode m) + { + _mode = m; + } + + private void CheckAnonymizationAttributesExist(AnonFunc f) + { + if (_testingmode) + { + return; + } + + if (f.Method.GetCustomAttributes(typeof(DescriptionAttribute), false).Length != 1) + { + throw new FormatException(string.Format("{0}:{1} Description attribute is missing", f.Target.GetType().FullName, f.Method.Name)); + } + } + + private void CheckExamplesExist(ITagHandler th, AnonFunc f) + { + if (_testingmode) + { + return; + } + + var exampMethod = th.GetType().GetMethod(f.Method.Name + "Examples"); + if (exampMethod == null) + { + throw new FormatException(string.Format("{0}:{1} Examples are missing.", f.Target.GetType().FullName, f.Method.Name)); + } + if (!exampMethod.IsStatic) + { + throw new FormatException(string.Format("{0}:{1} should be static.", f.Target.GetType().FullName, f.Method.Name)); + } + } + + /// + /// Register all actions of a TagHandler. If a tag is already handled, we throw an error. + /// + /// The tag handler from which tag actions will be consumed + public void RegisterHandler(ITagHandler th) + { + _tagHandlers.Add(th); + _postprocesses.Add(th); + + // TODO: check that handlers respect VR + th.GetTagFuncs()?.ToList().ForEach(x => + { + CheckAnonymizationAttributesExist(x.Value); + CheckExamplesExist(th, x.Value); + _tagFuncs.Add(x.Key, x.Value); + }); + th.GetRegexFuncs()?.ToList().ForEach(x => + { + CheckAnonymizationAttributesExist(x.Value); + CheckExamplesExist(th, x.Value); + _regexFuncs.Add(x.Key, x.Value); + }); + } + + /// + /// Register all actions of a TagHandler. Overwrites duplicates in favor of new actions. The actions can have side-effects. + /// + /// The tag handler from which tag actions will be consumed + public List ForceRegisterHandler(ITagHandler th) + { + _tagHandlers.Add(th); + _postprocesses.Add(th); + + var report = new List(); + th.GetTagFuncs()?.ToList().ForEach(x => + { + CheckAnonymizationAttributesExist(x.Value); + CheckExamplesExist(th, x.Value); + + if (_tagFuncs.ContainsKey(x.Key)) + { + report.Add(TagName(x.Key)); + } + + _tagFuncs[x.Key] = x.Value; + }); + th.GetRegexFuncs()?.ToList().ForEach(x => + { + CheckAnonymizationAttributesExist(x.Value); + CheckExamplesExist(th, x.Value); + + if (_regexFuncs.FirstOrDefault(pair => pair.Key.ToString().Equals(x.Key.ToString())).Key != null) + { + report.Add(x.Key.ToString()); + } + + _regexFuncs[x.Key] = x.Value; + }); + return report; + } + + private readonly Mode _mode; + + private DicomDataset ModeSwitch(DicomDataset ds, Mode m) + { + switch (m) + { + case Mode.inplace: + return ds; + case Mode.blank: + return new DicomDataset(); + default: + throw new InvalidOperationException(); + } + } + + private DicomDataset DoAnonymization(DicomDataset oldds, Stack stack, Mode m) + { + foreach (var th in _postprocesses) + { + th.NextDataset(); + } + + var newds = ModeSwitch(oldds, m); + var arr = oldds.ToList(); + + for (int i = 0; i < arr.Count; i++) + { + var item = arr[i]; + + // Use DFS to reach leaves first and then decide if we want + // to keep the sequence. This is not unoptimized code. The + // user might still need to visit the children even if it + // deletes the Seq. Eventually we should support Enter + // and Exit methods for Seqs. + if (item is DicomSequence) + { + DicomSequence nseq = new DicomSequence(item.Tag); + stack.Push(new TagOrIndex(item.Tag)); + // Visit sequence's children + foreach (var tuple in (item as DicomSequence).Items.ToList().Select((value, j) => new { j, value })) + { + var seqds = tuple.value; + var index = tuple.j; + + stack.Push(new TagOrIndex(index)); + + var n = DoAnonymization(seqds, stack, m); + // The only reason we get an empty DicomDataset is because + // we deleted its tags during recursion. + if (n.Count() > 0) // So we skip it + { + nseq.Items.Add(n); + } + if (seqds.Count() == 0) // AND we remove it from the original seq (this is for inplace mode) + { + (item as DicomSequence).Items.Remove(seqds); + } + + stack.Pop(); + } + item = nseq; + stack.Pop(); + } + + AnonFunc handler = null; + + // First we try to see if there is a string tag handler + if (_tagFuncs.ContainsKey(item.Tag)) + { + handler = _tagFuncs[item.Tag]; + } + else // If no string tag exists, check for regex + { + var tag = item.Tag.ToString().Trim(_parenthesis); + // Check against regex is rather slow as we need to visit all of them linearly + var regAct = _regexFuncs.FirstOrDefault(pair => pair.Key.IsMatch(tag)); + if (regAct.Key != null) + { + handler = regAct.Value; + } + } + + // No registered handler found + if (handler == null) + { + continue; + } + + // Item handler found + if (item is DicomElement || item is DicomSequence || item is DicomFragmentSequence) + { + // We don't include the item's tag in the path. + var r = handler(oldds, stack.Reverse().ToList(), item); + if (r != null) + { + newds.AddOrUpdate(r); + } + else + { + newds.Remove(item.Tag); + } + } + else + { + throw new InvalidOperationException($"Can't handle type: {item.GetType()}"); + } + + // TODO: Log which is the current iterated tag, and which function was invoked + } + + foreach (var th in _postprocesses) + { + th.Postprocess(newds); + } + return newds; + } + + /// + /// Anonymize a dicom dataset + /// + /// + /// + public DicomDataset Anonymize(DicomDataset dataset) + { + Mode m = _mode; + if (m == Mode.clone) + { + // TODO: Anonymizing DicomElement needs deep copy + // TODO: oldds and newds are pointless as newds' elements are pointers to oldds'. Any change affects both objects. + dataset = dataset.Clone(); + m = Mode.inplace; + } + + return DoAnonymization(dataset, new Stack(), m); + } + + /// Anonymizes a Dicom file (dataset + metadata) + /// The file containing the dataset to be altered + public DicomFile Anonymize(DicomFile file) + { + var transferSyntax = file.FileMetaInfo.Contains(DicomTag.TransferSyntaxUID) ? file.FileMetaInfo.TransferSyntax : null; + Mode m = _mode; + if (m == Mode.clone) + { + file = file.Clone(); + m = Mode.inplace; + } + + var ds = DoAnonymization(file.Dataset, new Stack(), m); + if (file.FileMetaInfo != null) + { + file = new DicomFile(ds); + file.FileMetaInfo.ImplementationVersionName = ""; + file.FileMetaInfo.SourceApplicationEntityTitle = ""; + if (transferSyntax != null) + { + file.FileMetaInfo.AddOrUpdate(DicomTag.TransferSyntaxUID, transferSyntax); + } + } + + return file; + } + + #region Reporting + + private string WrappingClass(AnonFunc f) + { + return f.Method.DeclaringType.FullName; + } + + private string Name(AnonFunc f) + { + return f.Method.Name; + } + + private string TagName(DicomTag t) + { + return t.DictionaryEntry.Name + " " + t.ToString().ToUpper(); + } + + private string Indent(int lvl) + { + var sb = new StringBuilder(); + for (int i = 0; i < lvl; i++) + { + sb.Append(" "); + } + return sb.ToString(); + } + + private Dictionary>> GroupTagsByTagFuncsAndTagHandler(IEnumerable> func_tag_stream) + { + // TagHandler => TagFunction => List + var grouped = new Dictionary>>(); + + foreach (var func_tag in func_tag_stream) + { + var func = func_tag.Key; + var tag = func_tag.Value; + + // TagFunction => List + if (grouped.TryGetValue(WrappingClass(func), out SortedDictionary> func_tags)) + { + if (!func_tags.ContainsKey(Name(func))) + { + func_tags[Name(func)] = new List { tag }; + } + else + { + func_tags[Name(func)].Add(tag); + } + } + else + { + func_tags = new SortedDictionary> { { Name(func), new List { tag } } }; + grouped.Add(WrappingClass(func), func_tags); + } + } + + return grouped; + } + + // TODO: Return a well structured XML/JSON + public List ReportRegisteredHandlers() + { + var report = new List(); + + // By reverse iterating the registered TagHandlers, we can detect + // configuration overwrites by checking if the same keys are + // registered twice. We reverse iterating as the latest class + // overwrites the previous ones. + var seen = new HashSet(); + var tmp_report = new List>(); + foreach (var th in _tagHandlers.Reverse()) + { + var conf = th.GetConfiguration(); + if (conf != null) + { + var tmp_l = new List { Indent(1) + "From: " + th.GetType().FullName }; + foreach (var kv in conf) + { + if (!seen.Contains(kv.Key)) + { + tmp_l.Add(Indent(2) + $"{kv.Key}: {kv.Value}"); + seen.Add(kv.Key); + } + } + tmp_report.Add(tmp_l); + } + } + // Reverse again, to report by registration order + report.Add("Current Configuration:"); + tmp_report.Reverse>().ToList().ForEach(l => report.AddRange(l)); + + (new Dictionary>>> { + { "Regex", GroupTagsByTagFuncsAndTagHandler(_regexFuncs.Select(p => new KeyValuePair(p.Value, p.Key.ToString()))) }, + { "Tag", GroupTagsByTagFuncsAndTagHandler(_tagFuncs.Select(p => new KeyValuePair(p.Value, TagName(p.Key)))) } + }).ToList() + .ForEach(type_groups => + { + var type = type_groups.Key; + var groups = type_groups.Value; + + // 2 types: Regex and Tag Functions + report.Add($"Current {type} Functions:"); + + // We need to report TagHandler with the order they been registered + _tagHandlers.ForEach(r => + { + var thName = r.GetType().FullName; + + if (groups.ContainsKey(thName)) + { + var func_tags = groups[thName]; + report.Add(""); + report.Add(Indent(1) + "From: " + thName); + + func_tags.ToList().ForEach(kv2 => + { + var tf = kv2.Key; + + // Gets the description attribute of each AnonFunc + var method = r.GetType().GetMethod(tf); + var attr = (DescriptionAttribute)method.GetCustomAttributes(typeof(DescriptionAttribute), false)[0]; + string desc = attr.Description; + + // Get the related AnonFunc examples + var exampMethod = r.GetType().GetMethod(tf + "Examples"); + var examples = exampMethod.Invoke(null, null) as List; + + var tags = kv2.Value; + tags.Sort(); + + report.Add(""); + report.Add(Indent(2) + "Functionality: " + tf); + report.Add(Indent(2) + "Description:"); + report.AddRange(Regex.Split(desc, "\r\n|\r|\n").Select(s => Indent(3) + s.Trim())); + report.Add(Indent(2) + "Examples:"); + int num = 0; + examples.ForEach(ex => + { + num++; + report.Add(Indent(3) + "Example " + num); + report.Add(Indent(4) + "Depending Input:"); + report.Add(string.Join(Environment.NewLine, ex.DependingInput.Select(s => Indent(5) + s))); + report.Add(Indent(4) + "Input:"); + report.Add(string.Join(Environment.NewLine, ex.Input.Select(s => Indent(5) + s))); + report.Add(Indent(4) + "Output:"); + report.Add(string.Join(Environment.NewLine, ex.Output.Select(s => Indent(5) + s))); + }); + report.Add(Indent(2) + "Tag List:"); + report.AddRange(tags.Select(t => Indent(3) + t)); + }); + } + }); + report.Add(""); + }); + + return report; + } + + public class AnonExample + { + public List DependingInput { get; } = new List(); + public List Input { get; } = new List(); + public List Output { get; } = new List(); + public List StateBefore { get; } = new List(); + public List StateAfter { get; } = new List(); + + public static void InferOutput(DicomItem input, DicomItem output, AnonExample example) + { + if (output == null) + { + example.Output.Add(""); + } + else if (input == output) + { + example.Output.Add(""); + } + else if (output as DicomElement != null) + { + if (output as DicomDate != null) + { + var v = (output as DicomDate).Get(); + example.Output.Add(v.ToShortDateString()); + } + else if (output as DicomTime != null) + { + var v = (output as DicomTime).Get(); + example.Output.Add(v.TimeOfDay.ToString()); + } + else + { + var v = (output as DicomElement).Get(); + example.Output.Add(v == "" ? "" : v); + } + } + } + } + + #endregion + + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer/ConfidentialityProfile.cs b/Source/Anonymizer/DICOMAnonymizer/ConfidentialityProfile.cs new file mode 100644 index 0000000..3e0715e --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer/ConfidentialityProfile.cs @@ -0,0 +1,678 @@ +using Dicom; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace DICOMAnonymizer +{ + using System.ComponentModel; + using static AnonymizeEngine; + using AnonFunc = Func, DicomItem, DicomItem>; + + public class ConfidentialityProfile : ITagHandler + { + + #region DICOM Confidentiality Profile + + public static readonly List TagProfile = new List + { + "0008,0050;Z;;;;;;;;;", + "0018,4000;X;;;;;;;C;;", + "0040,0555;X;;;;;;;;C;", + "0008,0022;X/Z;;;;;K;C;;;", + "0008,002A;X/D;;;;;K;C;;;", + "0018,1400;X/D;;;;;;;C;;", + "0018,9424;X;;;;;;;C;;", + "0008,0032;X/Z;;;;;K;C;;;", + "0040,4035;X;;;;;;;;;", + "0010,21B0;X;;;;;;;C;;", + "0040,A353;X;;;;;;;;;", + "0038,0010;X;;;;;;;;;", + "0038,0020;X;;;;;K;C;;;", + "0008,1084;X;;;;;;;C;;", + "0008,1080;X;;;;;;;C;;", + "0038,0021;X;;;;;K;C;;;", + "0000,1000;X;;K;;;;;;;", + "0010,2110;X;;;;C;;;C;;", + "4000,0010;X;;;;;;;;;", + "0040,A078;X;;;;;;;;;", + "0010,1081;X;;;;;;;;;", + "0018,1007;X;;;K;;;;;;", + "0040,0280;X;;;;;;;C;;", + "0020,9161;U;;K;;;;;;;", + "0040,3001;X;;;;;;;;;", + "0008,009D;X;;;;;;;;;", + "0008,009C;Z;;;;;;;;;", + "0070,0084;Z;;;;;;;;;", + "0070,0086;X;;;;;;;;;", + "0008,0023;Z/D;;;;;K;C;;;", + "0040,A730;X;;;;;;;;C;", + "0008,0033;Z/D;;;;;K;C;;;", + "0018,0010;Z/D;;;;;;;C;;", + "0018,A003;X;;;;;;;C;;", + "0010,2150;X;;;;;;;;;", + "0040,A307;X;;;;;;;;;", + "0038,0300;X;;;;;;;;;", + "0008,0025;X;;;;;K;C;;;", + "0008,0035;X;;;;;K;C;;;", + "0040,A07C;X;;;;;;;;;", + "FFFC,FFFC;X;;;;;;;;;", + "0008,2111;X;;;;;;;C;;", + "0018,700A;X/D;;;K;;;;;;", + "0018,1000;X/Z/D;;;K;;;;;;", + "0018,1002;U;;K;K;;;;;;", + "0400,0100;X;;;;;;;;;", + "FFFA,FFFA;X;;;;;;;;;", + "0020,9164;U;;K;;;;;;;", + "0038,0040;X;;;;;;;C;;", + "4008,011A;X;;;;;;;;;", + "4008,0119;X;;;;;;;;;", + "300A,0013;U;;K;;;;;;;", + "0018,9517;X/D;;;;;K;C;;;", + "0010,2160;X;;;;K;;;;;", + "0040,4011;X;;;;;K;C;;;", + "0008,0058;U;;K;;;;;;;", + "0070,031A;U;;K;;;;;;;", + "0040,2017;Z;;;;;;;;;", + "0020,9158;X;;;;;;;C;;", + "0020,0052;U;;K;;;;;;;", + "0018,1008;X;;;K;;;;;;", + "0018,1005;X;;;K;;;;;;", + "0070,0001;D;;;;;;;;;C", + "0040,4037;X;;;;;;;;;", + "0040,4036;X;;;;;;;;;", + "0088,0200;X;;;;;;;;;", + "0008,4000;X;;;;;;;C;;", + "0020,4000;X;;;;;;;C;;", + "0028,4000;X;;;;;;;;;", + "0040,2400;X;;;;;;;C;;", + "4008,0300;X;;;;;;;C;;", + "0008,0015;X;;;;;K;C;;;", + "0008,0014;U;;K;;;;;;;", + "0008,0081;X;;;;;;;;;", + "0008,0082;X/Z/D;;;;;;;;;", + "0008,0080;X/Z/D;;;;;;;;;", + "0008,1040;X;;;;;;;;;", + "0010,1050;X;;;;;;;;;", + "0040,1011;X;;;;;;;;;", + "4008,0111;X;;;;;;;;;", + "4008,010C;X;;;;;;;;;", + "4008,0115;X;;;;;;;C;;", + "4008,0202;X;;;;;;;;;", + "4008,0102;X;;;;;;;;;", + "4008,010B;X;;;;;;;C;;", + "4008,010A;X;;;;;;;;;", + "0008,3010;U;;K;;;;;;;", + "0038,0011;X;;;;;;;;;", + "0010,0021;X;;;;;;;;;", + "0038,0061;X;;;;;;;;;", + "0028,1214;U;;K;;;;;;;", + "0010,21D0;X;;;;;K;C;;;", + "0400,0404;X;;;;;;;;;", + "0002,0003;U;;K;;;;;;;", + "0010,2000;X;;;;;;;C;;", + "0010,1090;X;;;;;;;;;", + "0010,1080;X;;;;;;;;;", + "0400,0550;X;;;;;;;;;", + "0020,3406;X;;;;;;;;;", + "0020,3401;X;;;;;;;;;", + "0020,3404;X;;;;;;;;;", + "0008,1060;X;;;;;;;;;", + "0040,1010;X;;;;;;;;;", + "0040,A192;X;;;;;K;C;;;", + "0040,A402;U;;K;;;;;;;", + "0040,A193;X;;;;;K;C;;;", + "0040,A171;U;;K;;;;;;;", + "0010,2180;X;;;;;;;C;;", + "0008,1072;X/D;;;;;;;;;", + "0008,1070;X/Z/D;;;;;;;;;", + "0400,0561;X;;;;;;;;;", + "0040,2010;X;;;;;;;;;", + "0040,2011;X;;;;;;;;;", + "0040,2008;X;;;;;;;;;", + "0040,2009;X;;;;;;;;;", + "0010,1000;X;;;;;;;;;", + "0010,1002;X;;;;;;;;;", + "0010,1001;X;;;;;;;;;", + "0008,0024;X;;;;;K;C;;;", + "0008,0034;X;;;;;K;C;;;", + "0028,1199;U;;K;;;;;;;", + "0040,A07A;X;;;;;;;;;", + "0010,1040;X;;;;;;;;;", + "0010,4000;X;;;;;;;C;;", + "0010,0020;Z;;;;;;;;;", + "0010,2203;X/Z;;;;K;;;;;", + "0038,0500;X;;;;C;;;C;;", + "0040,1004;X;;;;;;;;;", + "0010,1010;X;;;;K;;;;;", + "0010,0030;Z;;;;;;;;;", + "0010,1005;X;;;;;;;;;", + "0010,0032;X;;;;;;;;;", + "0038,0400;X;;;;;;;;;", + "0010,0050;X;;;;;;;;;", + "0010,1060;X;;;;;;;;;", + "0010,0010;Z;;;;;;;;;", + "0010,0101;X;;;;;;;;;", + "0010,0102;X;;;;;;;;;", + "0010,21F0;X;;;;;;;;;", + "0010,0040;Z;;;;K;;;;;", + "0010,1020;X;;;;K;;;;;", + "0010,2155;X;;;;;;;;;", + "0010,2154;X;;;;;;;;;", + "0010,1030;X;;;;K;;;;;", + "0040,0243;X;;;;;;;;;", + "0040,0254;X;;;;;;;C;;", + "0040,0250;X;;;;;K;C;;;", + "0040,4051;X;;;;;K;C;;;", + "0040,0251;X;;;;;K;C;;;", + "0040,0253;X;;;;;;;;;", + "0040,0244;X;;;;;K;C;;;", + "0040,4050;X;;;;;K;C;;;", + "0040,0245;X;;;;;K;C;;;", + "0040,0241;X;;;K;;;;;;", + "0040,4030;X;;;K;;;;;;", + "0040,0242;X;;;K;;;;;;", + "0040,4028;X;;;K;;;;;;", + "0008,1052;X;;;;;;;;;", + "0008,1050;X;;;;;;;;;", + "0040,1102;X;;;;;;;;;", + "0040,1101;D;;;;;;;;;", + "0040,A123;D;;;;;;;;;", + "0040,1104;X;;;;;;;;;", + "0040,1103;X;;;;;;;;;", + "4008,0114;X;;;;;;;;;", + "0008,1062;X;;;;;;;;;", + "0008,1048;X;;;;;;;;;", + "0008,1049;X;;;;;;;;;", + "0040,2016;Z;;;;;;;;;", + "0018,1004;X;;;K;;;;;;", + "0040,0012;X;;;;C;;;;;", + "0010,21C0;X;;;;K;;;;;", + "0070,1101;U;;K;;;;;;;", + "0070,1102;U;;K;;;;;;;", + "0040,4052;X;;;;;K;C;;;", + "0018,1030;X/D;;;;;;;C;;", + "300C,0113;X;;;;;;;C;;", + "0040,2001;X;;;;;;;C;;", + "0032,1030;X;;;;;;;C;;", + "0400,0402;X;;;;;;;;;", + "3006,0024;U;;K;;;;;;;", + "0040,4023;U;;K;;;;;;;", + "0008,1140;X/Z/U*;;K;;;;;;;", + "0040,A172;U;;K;;;;;;;", + "0038,0004;X;;;;;;;;;", + "0010,1100;X;;;;;;;;;", + "0008,1120;X;;X;;;;;;;", + "0008,1111;X/Z/D;;K;;;;;;;", + "0400,0403;X;;;;;;;;;", + "0008,1155;U;;K;;;;;;;", + "0004,1511;U;;K;;;;;;;", + "0008,1110;X/Z;;K;;;;;;;", + "0008,0092;X;;;;;;;;;", + "0008,0096;X;;;;;;;;;", + "0008,0090;Z;;;;;;;;;", + "0008,0094;X;;;;;;;;;", + "0010,2152;X;;;;;;;;;", + "3006,00C2;U;;K;;;;;;;", + "0040,0275;X;;;;;;;C;;", + "0032,1070;X;;;;;;;C;;", + "0040,1400;X;;;;;;;C;;", + "0032,1060;X/Z;;;;;;;C;;", + "0040,1001;X;;;;;;;;;", + "0040,1005;X;;;;;;;;;", + "0000,1001;U;;K;;;;;;;", + "0032,1032;X;;;;;;;;;", + "0032,1033;X;;;;;;;;;", + "0010,2299;X;;;;;;;;;", + "0010,2297;X;;;;;;;;;", + "4008,4000;X;;;;;;;C;;", + "4008,0118;X;;;;;;;;;", + "4008,0042;X;;;;;;;;;", + "300E,0008;X/Z;;;;;;;;;", + "0040,4034;X;;;;;;;;;", + "0038,001E;X;;;;;;;;;", + "0040,000B;X;;;;;;;;;", + "0040,0006;X;;;;;;;;;", + "0040,0004;X;;;;;K;C;;;", + "0040,0005;X;;;;;K;C;;;", + "0040,0007;X;;;;;;;C;;", + "0040,0011;X;;;K;;;;;;", + "0040,4010;X;;;;;K;C;;;", + "0040,0002;X;;;;;K;C;;;", + "0040,4005;X;;;;;K;C;;;", + "0040,0003;X;;;;;K;C;;;", + "0040,0001;X;;;K;;;;;;", + "0040,4027;X;;;K;;;;;;", + "0040,0010;X;;;K;;;;;;", + "0040,4025;X;;;K;;;;;;", + "0032,1020;X;;;K;;;;;;", + "0032,1021;X;;;K;;;;;;", + "0008,0021;X/D;;;;;K;C;;;", + "0008,103E;X;;;;;;;C;;", + "0020,000E;U;;K;;;;;;;", + "0008,0031;X/D;;;;;K;C;;;", + "0038,0062;X;;;;;;;C;;", + "0038,0060;X;;;;;;;;;", + "0010,21A0;X;;;;K;;;;;", + "0008,0018;U;;K;;;;;;;", + "0008,2112;X/Z/U*;;K;;;;;;;", + "3008,0105;X;;;K;;;;;;", + "0038,0050;X;;;;C;;;;;", + "0018,9516;X/D;;;;;K;C;;;", + "0008,1010;X/Z/D;;;K;;;;;;", + "0088,0140;U;;K;;;;;;;", + "0032,4000;X;;;;;;;C;;", + "0008,0020;Z;;;;;K;C;;;", + "0008,1030;X;;;;;;;C;;", + "0020,0010;Z;;;;;;;;;", + "0032,0012;X;;;;;;;;;", + "0020,000D;U;;K;;;;;;;", + "0008,0030;Z;;;;;K;C;;;", + "0020,0200;U;;K;;;;;;;", + "0018,2042;U;;K;;;;;;;", + "0040,A354;X;;;;;;;;;", + "0040,DB0D;U;;K;;;;;;;", + "0040,DB0C;U;;K;;;;;;;", + "4000,4000;X;;;;;;;;;", + "2030,0020;X;;;;;;;;;", + "0008,0201;X;;;;;K;C;;;", + "0088,0910;X;;;;;;;;;", + "0088,0912;X;;;;;;;;;", + "0088,0906;X;;;;;;;;;", + "0088,0904;X;;;;;;;;;", + "0062,0021;U;;K;;;;;;;", + "0008,1195;U;;K;;;;;;;", + "0040,A124;U;;;;;;;;;", + "0040,A352;X;;;;;;;;;", + "0040,A358;X;;;;;;;;;", + "0040,A088;Z;;;;;;;;;", + "0040,A075;D;;;;;;;;;", + "0040,A073;D;;;;;;;;;", + "0040,A027;X;;;;;;;;;", + "0038,4000;X;;;;;;;C;;", + }; + + public static readonly List RegexProfile = new List + { + "[0-9A-F]{3}[13579BDF],[0-9A-F]{4};X;C;;;;;;;;", + "50[0-9A-F]{2},[0-9A-F]{4};X;;;;;;;;;C", + "60[0-9A-F]{2},4000;X;;;;;;;;;C", + "60[0-9A-F]{2},3000;X;;;;;;;;;C", + }; + + #endregion + + #region Options + + /// Profile options as described in DICOM PS 3.15-2017c + /// http://dicom.nema.org/medical/dicom/current/output/html/part15.html + /// The order of the flags are mapped to the profile's CSV file + [Flags] + public enum SecurityProfileOptions : short + { + BasicProfile = 1, + RetainSafePrivate = 2, + RetainUIDs = 4, + RetainDeviceIdent = 8, + RetainPatientChars = 16, + RetainLongFullDates = 32, + RetainLongModifDates = 64, + CleanDesc = 128, + CleanStructdCont = 256, + CleanGraph = 512, + } + + /// Profile actions per tag as described in DICOM PS 3.15-2017c + /// http://dicom.nema.org/medical/dicom/current/output/html/part15.html + public enum SecurityProfileActions : byte + { + D = 1, // Replace with a non-zero length value that may be a dummy value and consistent with the VR + Z = 2, // Replace with a zero length value, or a non-zero length value that may be a dummy value and consistent with the VR + X = 4, // Remove + K = 8, // Keep (unchanged for non-sequence attributes, cleaned for sequences) + C = 16, // Clean, that is replace with values of similar meaning known not to contain identifying information and consistent with the VR + U = 32, // Replace with a non-zero length UID that is internally consistent within a set of Instances + } + + #endregion + + private static readonly int _optionsCount = Enum.GetValues(typeof(SecurityProfileOptions)).Length; + private readonly SecurityProfileOptions _options; + + #region Helper methods + + /// + /// Return the tag and the appropriate action based on the security profile + /// + /// A valid confidentiality profile line + /// The security profile options + /// + private Tuple parseProfileItem(string item, SecurityProfileOptions options) + { + SecurityProfileActions? action = null; + var parts = item.Split(';'); + var tag = parts[0]; + + for (var i = 0; i < _optionsCount; i++) + { + var flag = (SecurityProfileOptions)(1 << i); + + if ((options & flag) == flag) + { + var a = parts[i + 1].ToCharArray().FirstOrDefault().ToString(); + if (a != default(char).ToString()) + { + action = (SecurityProfileActions)Enum.Parse(typeof(SecurityProfileActions), a); + } + } + } + + switch (action.Value) + { + case SecurityProfileActions.U: // UID + return Tuple.Create(tag, ReplaceUID); + case SecurityProfileActions.C: // Clean + case SecurityProfileActions.D: // Dummy + return Tuple.Create(tag, CleanDummyElement); + case SecurityProfileActions.K: // Keep + return Tuple.Create(tag, KeepItem); + case SecurityProfileActions.X: // Remove + return Tuple.Create(tag, RemoveItem); + case SecurityProfileActions.Z: // Zero-length + return Tuple.Create(tag, BlankElement); + default: + throw new ArgumentOutOfRangeException($"Unrecognized action: {action.Value}"); + } + } + + #endregion + + #region Constructors + + public ConfidentialityProfile() : this(SecurityProfileOptions.BasicProfile) { } + + public ConfidentialityProfile(SecurityProfileOptions ops) + { + _options = ops; + } + + #endregion + + public Dictionary GetRegexFuncs() + { + var regexActions = new Dictionary(); + + if (regexActions.Count == 0) + { + foreach (var item in RegexProfile) + { + var t = parseProfileItem(item, _options); + var tag = new Regex(t.Item1, RegexOptions.IgnoreCase | RegexOptions.Singleline); + var action = t.Item2; + + regexActions[tag] = t.Item2; + } + } + + return regexActions; + } + + public Dictionary GetTagFuncs() + { + var tagActions = new Dictionary(); + + if (tagActions.Count == 0) + { + foreach (var item in TagProfile) + { + var t = parseProfileItem(item, _options); + var tag = DicomTag.Parse(t.Item1); + var action = t.Item2; + + tagActions[tag] = t.Item2; + } + } + + return tagActions; + } + + public Dictionary GetConfiguration() + { + return new Dictionary + { + {"Security Profile Options", _options.ToString() }, + }; + } + + public void NextDataset() { } + + public void Postprocess(DicomDataset newds) { } + + #region Tag handler helpers + + /// + /// Use reflection to get strongly-typed constructor info from . + /// + /// DICOM element for which constructor info should be obtained. + /// Expected parameter types in the requested constructor. + /// Constructor info corresponding to and . + private static ConstructorInfo GetConstructor(DicomElement element, params Type[] parameterTypes) + { + return element.GetType().GetConstructor(parameterTypes); + } + + /// Evaluates whether an element is of type Other* + /// The element to be evaluated + /// A boolean flag indicating whether the element is of the expected type, otherwise false + public static bool IsOtherElement(DicomElement element) + { + var t = element.GetType(); + return t == typeof(DicomOtherByte) || t == typeof(DicomOtherDouble) || t == typeof(DicomOtherFloat) + || t == typeof(DicomOtherLong) || t == typeof(DicomOtherWord) || t == typeof(DicomUnknown); + } + + /// Evaluates whether an element has a generic valueType + /// The element to be evaluated + /// The data type if found, otherwise null + public static Type ElementValueType(DicomElement element) + { + var t = element.GetType(); + if (t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(DicomValueElement<>)) + { + return t.GenericTypeArguments[0]; + } + + return null; + } + + #endregion + + #region Tag handlers + + // TODO: Reset every new study? + /// Context/Output. Contains all the replaced UIDs. + /// Useful for consistency across a file set (multiple calls to anonymization methods) + public Dictionary ReplacedUIDs { get; } = new Dictionary(); + + /// Replaces the content of a UID with a random one + /// Reference to the old dataset + /// Reference to the new dataset + /// The element to be altered + [Description(@"We replace a UID with a new random one. This function keeps internal state so + the same UID tag in future Dicoms will be consistently replaced. To reset, + use the ReplacedUIDs property.")] + public DicomItem ReplaceUID(DicomDataset oldds, List path, DicomItem item) + { + var element = (item as DicomElement); + if (element == null) + { + throw new InvalidOperationException($"ReplaceUID can not handle type: {item.GetType()}"); + } + + if (element.ValueRepresentation != DicomVR.UI) + { + throw new ArgumentOutOfRangeException($"Tag: {element.Tag} is marked as U but VR is: {element.ValueRepresentation}"); + } + + string rep; + DicomUID uid; + var old = element.Get(); + + if (ReplacedUIDs.ContainsKey(old)) + { + rep = ReplacedUIDs[old]; + uid = new DicomUID(rep, "Anonymized UID", DicomUidType.Unknown); + } + else + { + uid = DicomUIDGenerator.GenerateDerivedFromUUID(); + rep = uid.UID; + ReplacedUIDs[old] = rep; + } + + return new DicomUniqueIdentifier(element.Tag, uid); + } + + [Description(@"Keeps the examined item but sets its value to be a dummy value. If the VR is a string + we replace it with the word 'ANONYMOUS', or else we set it to be empty.")] + public static DicomItem CleanDummyElement(DicomDataset oldds, List path, DicomItem item) + { + var vr = item.ValueRepresentation; + + if (vr.IsString) + { + // TODO: Needed to create a DicomItem with the correct VR + return (new DicomDataset()).AddOrUpdate(item.Tag, "ANONYMOUS").First(); + } + else + { + return BlankElement(oldds, path, item); + } + } + + /// Blanks an item by passing to it an empty string + /// Reference to the old dataset + /// The element to be processed + [Description("Keeps the examined item but sets its value to be empty.")] + public static DicomItem BlankElement(DicomDataset oldds, List path, DicomItem item) + { + var element = (item as DicomElement); + if (element == null) + { + return item; + } + + // Special date/time cases + if (element is DicomDateTime) + { + return new DicomDateTime(element.Tag, DateTime.MinValue); + } + if (element is DicomDate) + { + return new DicomDate(element.Tag, DateTime.MinValue); + } + if (element is DicomTime) + { + return new DicomTime(element.Tag, new DicomDateRange()); + } + + var stringElement = element as DicomStringElement; + if (stringElement != null) + { + // TODO: Needed to create a DicomItem with the correct VR + return (new DicomDataset()).AddOrUpdate(element.Tag, string.Empty).First(); + } + + if (IsOtherElement(element)) // Replaces with an empty array + { + var ctor = GetConstructor(element, typeof(DicomTag)); + var t = (DicomItem)ctor.Invoke(new object[] { element.Tag }); + + // TODO: Needed to create a DicomItem with the correct VR + return (new DicomDataset()).AddOrUpdate(element.Tag, t).First(); + } + + var valueType = ElementValueType(element); // Replace with the default value + if (valueType != null) + { + var ctor = GetConstructor(element, typeof(DicomTag), valueType); + var t = (DicomItem)ctor.Invoke(new[] { element.Tag, Activator.CreateInstance(valueType) }); + + // TODO: Needed to create a DicomItem with the correct VR + return (new DicomDataset()).AddOrUpdate(element.Tag, t).First(); + } + + throw new InvalidOperationException($"Missed type: {item.GetType()}"); + } + + /// + /// Removes an item by not adding it to the new dataset + /// + /// + /// + [Description("Removes the examined item.")] + public static DicomItem RemoveItem(DicomDataset oldds, List path, DicomItem item) { return null; } + + /// + /// Keeps an item by adding it to the new dataset + /// + /// Reference to the old dataset + /// The element to be processed + [Description("Keeps the examined item.")] + public static DicomItem KeepItem(DicomDataset oldds, List path, DicomItem item) { return item; } + + #endregion + + #region Tag handler examples + + public static List ReplaceUIDExamples() + { + return new List { }; + } + + public static List CleanDummyElementExamples() + { + return new List { }; + } + + public static List BlankElementExamples() + { + return new List { }; + } + + public static List RemoveItemExamples() + { + var output = new AnonExample(); + + var v = "Lorem Ipsum"; + var item = new DicomShortString(DicomTag.PerformedLocation, v); + output.Input.Add(item + ": " + v); + + var ans = RemoveItem(null, null, item); + + AnonExample.InferOutput(item, ans, output); + + return new List { output }; + } + + public static List KeepItemExamples() + { + var output = new AnonExample(); + + var v = "24.23"; + var item = new DicomDecimalString(DicomTag.PatientWeight, v); + output.Input.Add(item + ": " + v); + + var ans = KeepItem(null, null, item); + + AnonExample.InferOutput(item, ans, output); + + return new List { output }; + } + + #endregion + + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer/DICOMAnonymizer.csproj b/Source/Anonymizer/DICOMAnonymizer/DICOMAnonymizer.csproj new file mode 100644 index 0000000..358508f --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer/DICOMAnonymizer.csproj @@ -0,0 +1,25 @@ + + + net462 + x64 + DICOMAnonymizer + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + DICOM Tag Handling for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + \ No newline at end of file diff --git a/Source/Anonymizer/DICOMAnonymizer/ITagHandler.cs b/Source/Anonymizer/DICOMAnonymizer/ITagHandler.cs new file mode 100644 index 0000000..915b528 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer/ITagHandler.cs @@ -0,0 +1,42 @@ +using Dicom; +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace DICOMAnonymizer +{ + /// TODO: Ideally, argument DicomDataset oldds should be declared as immutable. + using AnonFunc = Func, DicomItem, DicomItem>; + + public interface ITagHandler + { + // TODO: + // 1) Ideally we want to expose functions with exactly the correct types per tag. + // e.g. a tag that is a UID should be Func + // or even: Func as its VR is string + // 2) Return values for Sequences should be bool. Or else users can edit the returned tag. + // 3) Maybe return a custom class that wraps operation (add,del,mod) with an object. + Dictionary GetTagFuncs(); + Dictionary GetRegexFuncs(); + + /// + /// Gives access to the processed dataset after the anonymization process. Should be + /// used for adding custom tags. + /// + /// + void Postprocess(DicomDataset newds); + + /// + /// Get the current configuration of a TagHandler. The anonymization engine can then + /// report the configuration options declared for each anonymization step. + /// + /// + Dictionary GetConfiguration(); + + /// + /// Inform the TagHandlers that a new Dataset will be processed. Use + /// to clean internal state. + /// + void NextDataset(); + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer/TagOrIndex.cs b/Source/Anonymizer/DICOMAnonymizer/TagOrIndex.cs new file mode 100644 index 0000000..451aec4 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer/TagOrIndex.cs @@ -0,0 +1,32 @@ +using Dicom; + +namespace DICOMAnonymizer +{ + public class TagOrIndex + { + public bool IsTag { get; } = false; + public DicomTag Tag { get; set; } = null; + public int Index { get; set; } = -1; + + public TagOrIndex(DicomTag tag) + { + Tag = tag; + IsTag = true; + } + + public TagOrIndex(int index) + { + Index = index; + IsTag = false; + } + + public override string ToString() + { + if (IsTag) + { + return Tag.ToString(); + } + return Index.ToString(); + } + } +} diff --git a/Source/Anonymizer/DICOMAnonymizer/Tools/SOPClassFinder.cs b/Source/Anonymizer/DICOMAnonymizer/Tools/SOPClassFinder.cs new file mode 100644 index 0000000..8f3c0a0 --- /dev/null +++ b/Source/Anonymizer/DICOMAnonymizer/Tools/SOPClassFinder.cs @@ -0,0 +1,425 @@ +using System.Collections.Generic; + +namespace DICOMAnonymizer.Tools +{ + public enum SOPClass + { + ComputedRadiographyImageStorage, + DigitalXRayImageStorageForPresentation, + DigitalXRayImageStorageForProcessing, + DigitalMammographyXRayImageStorageForPresentation, + DigitalMammographyXRayImageStorageForProcessing, + DigitalIntraOralXRayImageStorageForPresentation, + DigitalIntraOralXRayImageStorageForProcessing, + CTImageStorage, + EnhancedCTImageStorage, + LegacyConvertedEnhancedCTImageStorage, + UltrasoundMultiframeImageStorage, + MRImageStorage, + EnhancedMRImageStorage, + MRSpectroscopyStorage, + EnhancedMRColorImageStorage, + LegacyConvertedEnhancedMRImageStorage, + UltrasoundImageStorage, + EnhancedUSVolumeStorage, + SecondaryCaptureImageStorage, + MultiframeSingleBitSecondaryCaptureImageStorage, + MultiframeGrayscaleByteSecondaryCaptureImageStorage, + MultiframeGrayscaleWordSecondaryCaptureImageStorage, + MultiframeTrueColorSecondaryCaptureImageStorage, + _12leadECGWaveformStorage, + GeneralECGWaveformStorage, + AmbulatoryECGWaveformStorage, + HemodynamicWaveformStorage, + CardiacElectrophysiologyWaveformStorage, + BasicVoiceAudioWaveformStorage, + GeneralAudioWaveformStorage, + ArterialPulseWaveformStorage, + RespiratoryWaveformStorage, + GrayscaleSoftcopyPresentationStateStorage, + ColorSoftcopyPresentationStateStorage, + PseudoColorSoftcopyPresentationStateStorage, + BlendingSoftcopyPresentationStateStorage, + XAXRFGrayscaleSoftcopyPresentationStateStorage, + GrayscalePlanarMPRVolumetricPresentationStateStorage, + CompositingPlanarMPRVolumetricPresentationStateStorage, + AdvancedBlendingPresentationStateStorage, + VolumeRenderingVolumetricPresentationStateStorage, + SegmentedVolumeRenderingVolumetricPresentationStateStorage, + MultipleVolumeRenderingVolumetricPresentationStateStorage, + XRayAngiographicImageStorage, + EnhancedXAImageStorage, + XRayRadiofluoroscopicImageStorage, + EnhancedXRFImageStorage, + XRay3DAngiographicImageStorage, + XRay3DCraniofacialImageStorage, + BreastTomosynthesisImageStorage, + BreastProjectionXRayImageStorageForPresentation, + BreastProjectionXRayImageStorageForProcessing, + IntravascularOpticalCoherenceTomographyImageStorageForPresentation, + IntravascularOpticalCoherenceTomographyImageStorageForProcessing, + NuclearMedicineImageStorage, + ParametricMapStorage, + RawDataStorage, + SpatialRegistrationStorage, + SpatialFiducialsStorage, + DeformableSpatialRegistrationStorage, + SegmentationStorage, + SurfaceSegmentationStorage, + TractographyResultsStorage, + RealWorldValueMappingStorage, + SurfaceScanMeshStorage, + SurfaceScanPointCloudStorage, + VLEndoscopicImageStorage, + VideoEndoscopicImageStorage, + VLMicroscopicImageStorage, + VideoMicroscopicImageStorage, + VLSlideCoordinatesMicroscopicImageStorage, + VLPhotographicImageStorage, + VideoPhotographicImageStorage, + OphthalmicPhotography8BitImageStorage, + OphthalmicPhotography16BitImageStorage, + StereometricRelationshipStorage, + OphthalmicTomographyImageStorage, + WideFieldOphthalmicPhotographyStereographicProjectionImageStorage, + WideFieldOphthalmicPhotography3DCoordinatesImageStorage, + OphthalmicOpticalCoherenceTomographyEnFaceImageStorage, + OphthalmicOpticalCoherenceTomographyBscanVolumeAnalysisStorage, + VLWholeSlideMicroscopyImageStorage, + LensometryMeasurementsStorage, + AutorefractionMeasurementsStorage, + KeratometryMeasurementsStorage, + SubjectiveRefractionMeasurementsStorage, + VisualAcuityMeasurementsStorage, + SpectaclePrescriptionReportStorage, + OphthalmicAxialMeasurementsStorage, + IntraocularLensCalculationsStorage, + MacularGridThicknessandVolumeReport, + OphthalmicVisualFieldStaticPerimetryMeasurementsStorage, + OphthalmicThicknessMapStorage, + CornealTopographyMapStorage, + BasicTextSRStorage, + EnhancedSRStorage, + ComprehensiveSRStorage, + Comprehensive3DSRStorage, + ExtensibleSRStorage, + ProcedureLogStorage, + MammographyCADSRStorage, + KeyObjectSelectionStorage, + ChestCADSRStorage, + XRayRadiationDoseSRStorage, + RadiopharmaceuticalRadiationDoseSRStorage, + ColonCADSRStorage, + ImplantationPlanSRDocumentStorage, + AcquisitionContextSRStorage, + SimplifiedAdultEchoSRStorage, + PatientRadiationDoseSRStorage, + ContentAssessmentResultsStorage, + EncapsulatedPDFStorage, + EncapsulatedCDAStorage, + PositronEmissionTomographyImageStorage, + EnhancedPETImageStorage, + LegacyConvertedEnhancedPETImageStorage, + BasicStructuredDisplayStorage, + CTPerformedProcedureProtocolStorage, + RTImageStorage, + RTDoseStorage, + RTStructureSetStorage, + RTBeamsTreatmentRecordStorage, + RTPlanStorage, + RTBrachyTreatmentRecordStorage, + RTTreatmentSummaryRecordStorage, + RTIonPlanStorage, + RTIonBeamsTreatmentRecordStorage, + RTBeamsDeliveryInstructionStorage, + RTBrachyApplicationSetupDeliveryInstructionStorage, + } + + public class SOPClassFinder + { + + #region SOP Class Dictionaries + + private static readonly Dictionary _sopNameMap = new Dictionary() + { + {SOPClass.ComputedRadiographyImageStorage, "1.2.840.10008.5.1.4.1.1.1"}, + {SOPClass.DigitalXRayImageStorageForPresentation, "1.2.840.10008.5.1.4.1.1.1.1"}, + {SOPClass.DigitalXRayImageStorageForProcessing, "1.2.840.10008.5.1.4.1.1.1.1.1"}, + {SOPClass.DigitalMammographyXRayImageStorageForPresentation, "1.2.840.10008.5.1.4.1.1.1.2"}, + {SOPClass.DigitalMammographyXRayImageStorageForProcessing, "1.2.840.10008.5.1.4.1.1.1.2.1"}, + {SOPClass.DigitalIntraOralXRayImageStorageForPresentation, "1.2.840.10008.5.1.4.1.1.1.3"}, + {SOPClass.DigitalIntraOralXRayImageStorageForProcessing, "1.2.840.10008.5.1.4.1.1.1.3.1"}, + {SOPClass.CTImageStorage, "1.2.840.10008.5.1.4.1.1.2"}, + {SOPClass.EnhancedCTImageStorage, "1.2.840.10008.5.1.4.1.1.2.1"}, + {SOPClass.LegacyConvertedEnhancedCTImageStorage, "1.2.840.10008.5.1.4.1.1.2.2"}, + {SOPClass.UltrasoundMultiframeImageStorage, "1.2.840.10008.5.1.4.1.1.3.1"}, + {SOPClass.MRImageStorage, "1.2.840.10008.5.1.4.1.1.4"}, + {SOPClass.EnhancedMRImageStorage, "1.2.840.10008.5.1.4.1.1.4.1"}, + {SOPClass.MRSpectroscopyStorage, "1.2.840.10008.5.1.4.1.1.4.2"}, + {SOPClass.EnhancedMRColorImageStorage, "1.2.840.10008.5.1.4.1.1.4.3"}, + {SOPClass.LegacyConvertedEnhancedMRImageStorage, "1.2.840.10008.5.1.4.1.1.4.4"}, + {SOPClass.UltrasoundImageStorage, "1.2.840.10008.5.1.4.1.1.6.1"}, + {SOPClass.EnhancedUSVolumeStorage, "1.2.840.10008.5.1.4.1.1.6.2"}, + {SOPClass.SecondaryCaptureImageStorage, "1.2.840.10008.5.1.4.1.1.7"}, + {SOPClass.MultiframeSingleBitSecondaryCaptureImageStorage, "1.2.840.10008.5.1.4.1.1.7.1"}, + {SOPClass.MultiframeGrayscaleByteSecondaryCaptureImageStorage, "1.2.840.10008.5.1.4.1.1.7.2"}, + {SOPClass.MultiframeGrayscaleWordSecondaryCaptureImageStorage, "1.2.840.10008.5.1.4.1.1.7.3"}, + {SOPClass.MultiframeTrueColorSecondaryCaptureImageStorage, "1.2.840.10008.5.1.4.1.1.7.4"}, + {SOPClass._12leadECGWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.1.1"}, + {SOPClass.GeneralECGWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.1.2"}, + {SOPClass.AmbulatoryECGWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.1.3"}, + {SOPClass.HemodynamicWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.2.1"}, + {SOPClass.CardiacElectrophysiologyWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.3.1"}, + {SOPClass.BasicVoiceAudioWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.4.1"}, + {SOPClass.GeneralAudioWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.4.2"}, + {SOPClass.ArterialPulseWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.5.1"}, + {SOPClass.RespiratoryWaveformStorage, "1.2.840.10008.5.1.4.1.1.9.6.1"}, + {SOPClass.GrayscaleSoftcopyPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.1"}, + {SOPClass.ColorSoftcopyPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.2"}, + {SOPClass.PseudoColorSoftcopyPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.3"}, + {SOPClass.BlendingSoftcopyPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.4"}, + {SOPClass.XAXRFGrayscaleSoftcopyPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.5"}, + {SOPClass.GrayscalePlanarMPRVolumetricPresentationStateStorage, "1.2.840.10008.​5.​1.​4.​1.​1.​11.​6"}, + {SOPClass.CompositingPlanarMPRVolumetricPresentationStateStorage, "1.2.840.10008.​5.​1.​4.​1.​1.​11.​7"}, + {SOPClass.AdvancedBlendingPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.8"}, + {SOPClass.VolumeRenderingVolumetricPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.9"}, + {SOPClass.SegmentedVolumeRenderingVolumetricPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.10"}, + {SOPClass.MultipleVolumeRenderingVolumetricPresentationStateStorage, "1.2.840.10008.5.1.4.1.1.11.11"}, + {SOPClass.XRayAngiographicImageStorage, "1.2.840.10008.5.1.4.1.1.12.1"}, + {SOPClass.EnhancedXAImageStorage, "1.2.840.10008.5.1.4.1.1.12.1.1"}, + {SOPClass.XRayRadiofluoroscopicImageStorage, "1.2.840.10008.5.1.4.1.1.12.2"}, + {SOPClass.EnhancedXRFImageStorage, "1.2.840.10008.5.1.4.1.1.12.2.1"}, + {SOPClass.XRay3DAngiographicImageStorage, "1.2.840.10008.5.1.4.1.1.13.1.1"}, + {SOPClass.XRay3DCraniofacialImageStorage, "1.2.840.10008.5.1.4.1.1.13.1.2"}, + {SOPClass.BreastTomosynthesisImageStorage, "1.2.840.10008.5.1.4.1.1.13.1.3"}, + {SOPClass.BreastProjectionXRayImageStorageForPresentation, "1.2.840.10008.5.1.4.1.1.13.1.4"}, + {SOPClass.BreastProjectionXRayImageStorageForProcessing, "1.2.840.10008.5.1.4.1.1.13.1.5"}, + {SOPClass.IntravascularOpticalCoherenceTomographyImageStorageForPresentation, "1.2.840.10008.5.1.4.1.1.14.1"}, + {SOPClass.IntravascularOpticalCoherenceTomographyImageStorageForProcessing, "1.2.840.10008.5.1.4.1.1.14.2"}, + {SOPClass.NuclearMedicineImageStorage, "1.2.840.10008.5.1.4.1.1.20"}, + {SOPClass.ParametricMapStorage, "1.2.840.10008.5.1.4.1.1.30"}, + {SOPClass.RawDataStorage, "1.2.840.10008.5.1.4.1.1.66"}, + {SOPClass.SpatialRegistrationStorage, "1.2.840.10008.5.1.4.1.1.66.1"}, + {SOPClass.SpatialFiducialsStorage, "1.2.840.10008.5.1.4.1.1.66.2"}, + {SOPClass.DeformableSpatialRegistrationStorage, "1.2.840.10008.5.1.4.1.1.66.3"}, + {SOPClass.SegmentationStorage, "1.2.840.10008.5.1.4.1.1.66.4"}, + {SOPClass.SurfaceSegmentationStorage, "1.2.840.10008.5.1.4.1.1.66.5"}, + {SOPClass.TractographyResultsStorage, "1.2.840.10008.5.1.4.1.1.66.6"}, + {SOPClass.RealWorldValueMappingStorage, "1.2.840.10008.5.1.4.1.1.67"}, + {SOPClass.SurfaceScanMeshStorage, "1.2.840.10008.5.1.4.1.1.68.1"}, + {SOPClass.SurfaceScanPointCloudStorage, "1.2.840.10008.5.1.4.1.1.68.2"}, + {SOPClass.VLEndoscopicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.1"}, + {SOPClass.VideoEndoscopicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.1.1"}, + {SOPClass.VLMicroscopicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.2"}, + {SOPClass.VideoMicroscopicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.2.1"}, + {SOPClass.VLSlideCoordinatesMicroscopicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.3"}, + {SOPClass.VLPhotographicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.4"}, + {SOPClass.VideoPhotographicImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.4.1"}, + {SOPClass.OphthalmicPhotography8BitImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.1"}, + {SOPClass.OphthalmicPhotography16BitImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.2"}, + {SOPClass.StereometricRelationshipStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.3"}, + {SOPClass.OphthalmicTomographyImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.4"}, + {SOPClass.WideFieldOphthalmicPhotographyStereographicProjectionImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.5"}, + {SOPClass.WideFieldOphthalmicPhotography3DCoordinatesImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.6"}, + {SOPClass.OphthalmicOpticalCoherenceTomographyEnFaceImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.7"}, + {SOPClass.OphthalmicOpticalCoherenceTomographyBscanVolumeAnalysisStorage, "1.2.840.10008.5.1.4.1.1.77.1.5.8"}, + {SOPClass.VLWholeSlideMicroscopyImageStorage, "1.2.840.10008.5.1.4.1.1.77.1.6"}, + {SOPClass.LensometryMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.78.1"}, + {SOPClass.AutorefractionMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.78.2"}, + {SOPClass.KeratometryMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.78.3"}, + {SOPClass.SubjectiveRefractionMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.78.4"}, + {SOPClass.VisualAcuityMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.78.5"}, + {SOPClass.SpectaclePrescriptionReportStorage, "1.2.840.10008.5.1.4.1.1.78.6"}, + {SOPClass.OphthalmicAxialMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.78.7"}, + {SOPClass.IntraocularLensCalculationsStorage, "1.2.840.10008.5.1.4.1.1.78.8"}, + {SOPClass.MacularGridThicknessandVolumeReport, "1.2.840.10008.5.1.4.1.1.79.1"}, + {SOPClass.OphthalmicVisualFieldStaticPerimetryMeasurementsStorage, "1.2.840.10008.5.1.4.1.1.80.1"}, + {SOPClass.OphthalmicThicknessMapStorage, "1.2.840.10008.5.1.4.1.1.81.1"}, + {SOPClass.CornealTopographyMapStorage, "1.2.840.10008.5.1.4.1.1.82.1"}, + {SOPClass.BasicTextSRStorage, "1.2.840.10008.5.1.4.1.1.88.11"}, + {SOPClass.EnhancedSRStorage, "1.2.840.10008.5.1.4.1.1.88.22"}, + {SOPClass.ComprehensiveSRStorage, "1.2.840.10008.5.1.4.1.1.88.33"}, + {SOPClass.Comprehensive3DSRStorage, "1.2.840.10008.5.1.4.1.1.88.34"}, + {SOPClass.ExtensibleSRStorage, "1.2.840.10008.5.1.4.1.1.88.35"}, + {SOPClass.ProcedureLogStorage, "1.2.840.10008.5.1.4.1.1.88.40"}, + {SOPClass.MammographyCADSRStorage, "1.2.840.10008.5.1.4.1.1.88.50"}, + {SOPClass.KeyObjectSelectionStorage, "1.2.840.10008.5.1.4.1.1.88.59"}, + {SOPClass.ChestCADSRStorage, "1.2.840.10008.5.1.4.1.1.88.65"}, + {SOPClass.XRayRadiationDoseSRStorage, "1.2.840.10008.5.1.4.1.1.88.67"}, + {SOPClass.RadiopharmaceuticalRadiationDoseSRStorage, "1.2.840.10008.5.1.4.1.1.88.68"}, + {SOPClass.ColonCADSRStorage, "1.2.840.10008.5.1.4.1.1.88.69"}, + {SOPClass.ImplantationPlanSRDocumentStorage, "1.2.840.10008.5.1.4.1.1.88.70"}, + {SOPClass.AcquisitionContextSRStorage, "1.2.840.10008.5.​1.​4.​1.​1.​88.​71"}, + {SOPClass.SimplifiedAdultEchoSRStorage, "1.2.840.10008.5.​1.​4.​1.​1.​88.​72"}, + {SOPClass.PatientRadiationDoseSRStorage, "1.2.840.10008.5.​1.​4.​1.​1.​88.​73"}, + {SOPClass.ContentAssessmentResultsStorage, "1.2.840.10008.5.1.4.1.1.90.1"}, + {SOPClass.EncapsulatedPDFStorage, "1.2.840.10008.5.1.4.1.1.104.1"}, + {SOPClass.EncapsulatedCDAStorage, "1.2.840.10008.5.1.4.1.1.104.2"}, + {SOPClass.PositronEmissionTomographyImageStorage, "1.2.840.10008.5.1.4.1.1.128"}, + {SOPClass.EnhancedPETImageStorage, "1.2.840.10008.5.1.4.1.1.130"}, + {SOPClass.LegacyConvertedEnhancedPETImageStorage, "1.2.840.10008.5.1.4.1.1.128.1"}, + {SOPClass.BasicStructuredDisplayStorage, "1.2.840.10008.5.1.4.1.1.131"}, + {SOPClass.CTPerformedProcedureProtocolStorage, "1.2.840.10008.5.1.4.1.1.200.2"}, + {SOPClass.RTImageStorage, "1.2.840.10008.5.1.4.1.1.481.1"}, + {SOPClass.RTDoseStorage, "1.2.840.10008.5.1.4.1.1.481.2"}, + {SOPClass.RTStructureSetStorage, "1.2.840.10008.5.1.4.1.1.481.3"}, + {SOPClass.RTBeamsTreatmentRecordStorage, "1.2.840.10008.5.1.4.1.1.481.4"}, + {SOPClass.RTPlanStorage, "1.2.840.10008.5.1.4.1.1.481.5"}, + {SOPClass.RTBrachyTreatmentRecordStorage, "1.2.840.10008.5.1.4.1.1.481.6"}, + {SOPClass.RTTreatmentSummaryRecordStorage, "1.2.840.10008.5.1.4.1.1.481.7"}, + {SOPClass.RTIonPlanStorage, "1.2.840.10008.5.1.4.1.1.481.8"}, + {SOPClass.RTIonBeamsTreatmentRecordStorage, "1.2.840.10008.5.1.4.1.1.481.9"}, + {SOPClass.RTBeamsDeliveryInstructionStorage, "1.2.840.10008.5.1.4.34.7"}, + {SOPClass.RTBrachyApplicationSetupDeliveryInstructionStorage, "1.2.840.10008.5.1.4.34.10"}, + }; + + private static readonly Dictionary _sopCodeMap = new Dictionary() + { + {"1.2.840.10008.5.1.4.1.1.1", SOPClass.ComputedRadiographyImageStorage}, + {"1.2.840.10008.5.1.4.1.1.1.1", SOPClass.DigitalXRayImageStorageForPresentation}, + {"1.2.840.10008.5.1.4.1.1.1.1.1", SOPClass.DigitalXRayImageStorageForProcessing}, + {"1.2.840.10008.5.1.4.1.1.1.2", SOPClass.DigitalMammographyXRayImageStorageForPresentation}, + {"1.2.840.10008.5.1.4.1.1.1.2.1", SOPClass.DigitalMammographyXRayImageStorageForProcessing}, + {"1.2.840.10008.5.1.4.1.1.1.3", SOPClass.DigitalIntraOralXRayImageStorageForPresentation}, + {"1.2.840.10008.5.1.4.1.1.1.3.1", SOPClass.DigitalIntraOralXRayImageStorageForProcessing}, + {"1.2.840.10008.5.1.4.1.1.2", SOPClass.CTImageStorage}, + {"1.2.840.10008.5.1.4.1.1.2.1", SOPClass.EnhancedCTImageStorage}, + {"1.2.840.10008.5.1.4.1.1.2.2", SOPClass.LegacyConvertedEnhancedCTImageStorage}, + {"1.2.840.10008.5.1.4.1.1.3.1", SOPClass.UltrasoundMultiframeImageStorage}, + {"1.2.840.10008.5.1.4.1.1.4", SOPClass.MRImageStorage}, + {"1.2.840.10008.5.1.4.1.1.4.1", SOPClass.EnhancedMRImageStorage}, + {"1.2.840.10008.5.1.4.1.1.4.2", SOPClass.MRSpectroscopyStorage}, + {"1.2.840.10008.5.1.4.1.1.4.3", SOPClass.EnhancedMRColorImageStorage}, + {"1.2.840.10008.5.1.4.1.1.4.4", SOPClass.LegacyConvertedEnhancedMRImageStorage}, + {"1.2.840.10008.5.1.4.1.1.6.1", SOPClass.UltrasoundImageStorage}, + {"1.2.840.10008.5.1.4.1.1.6.2", SOPClass.EnhancedUSVolumeStorage}, + {"1.2.840.10008.5.1.4.1.1.7", SOPClass.SecondaryCaptureImageStorage}, + {"1.2.840.10008.5.1.4.1.1.7.1", SOPClass.MultiframeSingleBitSecondaryCaptureImageStorage}, + {"1.2.840.10008.5.1.4.1.1.7.2", SOPClass.MultiframeGrayscaleByteSecondaryCaptureImageStorage}, + {"1.2.840.10008.5.1.4.1.1.7.3", SOPClass.MultiframeGrayscaleWordSecondaryCaptureImageStorage}, + {"1.2.840.10008.5.1.4.1.1.7.4", SOPClass.MultiframeTrueColorSecondaryCaptureImageStorage}, + {"1.2.840.10008.5.1.4.1.1.9.1.1", SOPClass._12leadECGWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.1.2", SOPClass.GeneralECGWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.1.3", SOPClass.AmbulatoryECGWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.2.1", SOPClass.HemodynamicWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.3.1", SOPClass.CardiacElectrophysiologyWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.4.1", SOPClass.BasicVoiceAudioWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.4.2", SOPClass.GeneralAudioWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.5.1", SOPClass.ArterialPulseWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.9.6.1", SOPClass.RespiratoryWaveformStorage}, + {"1.2.840.10008.5.1.4.1.1.11.1", SOPClass.GrayscaleSoftcopyPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.2", SOPClass.ColorSoftcopyPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.3", SOPClass.PseudoColorSoftcopyPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.4", SOPClass.BlendingSoftcopyPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.5", SOPClass.XAXRFGrayscaleSoftcopyPresentationStateStorage}, + {"1.2.840.10008.​5.​1.​4.​1.​1.​11.​6", SOPClass.GrayscalePlanarMPRVolumetricPresentationStateStorage}, + {"1.2.840.10008.​5.​1.​4.​1.​1.​11.​7", SOPClass.CompositingPlanarMPRVolumetricPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.8", SOPClass.AdvancedBlendingPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.9", SOPClass.VolumeRenderingVolumetricPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.10", SOPClass.SegmentedVolumeRenderingVolumetricPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.11.11", SOPClass.MultipleVolumeRenderingVolumetricPresentationStateStorage}, + {"1.2.840.10008.5.1.4.1.1.12.1", SOPClass.XRayAngiographicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.12.1.1", SOPClass.EnhancedXAImageStorage}, + {"1.2.840.10008.5.1.4.1.1.12.2", SOPClass.XRayRadiofluoroscopicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.12.2.1", SOPClass.EnhancedXRFImageStorage}, + {"1.2.840.10008.5.1.4.1.1.13.1.1", SOPClass.XRay3DAngiographicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.13.1.2", SOPClass.XRay3DCraniofacialImageStorage}, + {"1.2.840.10008.5.1.4.1.1.13.1.3", SOPClass.BreastTomosynthesisImageStorage}, + {"1.2.840.10008.5.1.4.1.1.13.1.4", SOPClass.BreastProjectionXRayImageStorageForPresentation}, + {"1.2.840.10008.5.1.4.1.1.13.1.5", SOPClass.BreastProjectionXRayImageStorageForProcessing}, + {"1.2.840.10008.5.1.4.1.1.14.1", SOPClass.IntravascularOpticalCoherenceTomographyImageStorageForPresentation}, + {"1.2.840.10008.5.1.4.1.1.14.2", SOPClass.IntravascularOpticalCoherenceTomographyImageStorageForProcessing}, + {"1.2.840.10008.5.1.4.1.1.20", SOPClass.NuclearMedicineImageStorage}, + {"1.2.840.10008.5.1.4.1.1.30", SOPClass.ParametricMapStorage}, + {"1.2.840.10008.5.1.4.1.1.66", SOPClass.RawDataStorage}, + {"1.2.840.10008.5.1.4.1.1.66.1", SOPClass.SpatialRegistrationStorage}, + {"1.2.840.10008.5.1.4.1.1.66.2", SOPClass.SpatialFiducialsStorage}, + {"1.2.840.10008.5.1.4.1.1.66.3", SOPClass.DeformableSpatialRegistrationStorage}, + {"1.2.840.10008.5.1.4.1.1.66.4", SOPClass.SegmentationStorage}, + {"1.2.840.10008.5.1.4.1.1.66.5", SOPClass.SurfaceSegmentationStorage}, + {"1.2.840.10008.5.1.4.1.1.66.6", SOPClass.TractographyResultsStorage}, + {"1.2.840.10008.5.1.4.1.1.67", SOPClass.RealWorldValueMappingStorage}, + {"1.2.840.10008.5.1.4.1.1.68.1", SOPClass.SurfaceScanMeshStorage}, + {"1.2.840.10008.5.1.4.1.1.68.2", SOPClass.SurfaceScanPointCloudStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.1", SOPClass.VLEndoscopicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.1.1", SOPClass.VideoEndoscopicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.2", SOPClass.VLMicroscopicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.2.1", SOPClass.VideoMicroscopicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.3", SOPClass.VLSlideCoordinatesMicroscopicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.4", SOPClass.VLPhotographicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.4.1", SOPClass.VideoPhotographicImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.1", SOPClass.OphthalmicPhotography8BitImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.2", SOPClass.OphthalmicPhotography16BitImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.3", SOPClass.StereometricRelationshipStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.4", SOPClass.OphthalmicTomographyImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.5", SOPClass.WideFieldOphthalmicPhotographyStereographicProjectionImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.6", SOPClass.WideFieldOphthalmicPhotography3DCoordinatesImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.7", SOPClass.OphthalmicOpticalCoherenceTomographyEnFaceImageStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.5.8", SOPClass.OphthalmicOpticalCoherenceTomographyBscanVolumeAnalysisStorage}, + {"1.2.840.10008.5.1.4.1.1.77.1.6", SOPClass.VLWholeSlideMicroscopyImageStorage}, + {"1.2.840.10008.5.1.4.1.1.78.1", SOPClass.LensometryMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.78.2", SOPClass.AutorefractionMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.78.3", SOPClass.KeratometryMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.78.4", SOPClass.SubjectiveRefractionMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.78.5", SOPClass.VisualAcuityMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.78.6", SOPClass.SpectaclePrescriptionReportStorage}, + {"1.2.840.10008.5.1.4.1.1.78.7", SOPClass.OphthalmicAxialMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.78.8", SOPClass.IntraocularLensCalculationsStorage}, + {"1.2.840.10008.5.1.4.1.1.79.1", SOPClass.MacularGridThicknessandVolumeReport}, + {"1.2.840.10008.5.1.4.1.1.80.1", SOPClass.OphthalmicVisualFieldStaticPerimetryMeasurementsStorage}, + {"1.2.840.10008.5.1.4.1.1.81.1", SOPClass.OphthalmicThicknessMapStorage}, + {"1.2.840.10008.5.1.4.1.1.82.1", SOPClass.CornealTopographyMapStorage}, + {"1.2.840.10008.5.1.4.1.1.88.11", SOPClass.BasicTextSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.22", SOPClass.EnhancedSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.33", SOPClass.ComprehensiveSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.34", SOPClass.Comprehensive3DSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.35", SOPClass.ExtensibleSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.40", SOPClass.ProcedureLogStorage}, + {"1.2.840.10008.5.1.4.1.1.88.50", SOPClass.MammographyCADSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.59", SOPClass.KeyObjectSelectionStorage}, + {"1.2.840.10008.5.1.4.1.1.88.65", SOPClass.ChestCADSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.67", SOPClass.XRayRadiationDoseSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.68", SOPClass.RadiopharmaceuticalRadiationDoseSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.69", SOPClass.ColonCADSRStorage}, + {"1.2.840.10008.5.1.4.1.1.88.70", SOPClass.ImplantationPlanSRDocumentStorage}, + {"1.2.840.10008.5.​1.​4.​1.​1.​88.​71", SOPClass.AcquisitionContextSRStorage}, + {"1.2.840.10008.5.​1.​4.​1.​1.​88.​72", SOPClass.SimplifiedAdultEchoSRStorage}, + {"1.2.840.10008.5.​1.​4.​1.​1.​88.​73", SOPClass.PatientRadiationDoseSRStorage}, + {"1.2.840.10008.5.1.4.1.1.90.1", SOPClass.ContentAssessmentResultsStorage}, + {"1.2.840.10008.5.1.4.1.1.104.1", SOPClass.EncapsulatedPDFStorage}, + {"1.2.840.10008.5.1.4.1.1.104.2", SOPClass.EncapsulatedCDAStorage}, + {"1.2.840.10008.5.1.4.1.1.128", SOPClass.PositronEmissionTomographyImageStorage}, + {"1.2.840.10008.5.1.4.1.1.130", SOPClass.EnhancedPETImageStorage}, + {"1.2.840.10008.5.1.4.1.1.128.1", SOPClass.LegacyConvertedEnhancedPETImageStorage}, + {"1.2.840.10008.5.1.4.1.1.131", SOPClass.BasicStructuredDisplayStorage}, + {"1.2.840.10008.5.1.4.1.1.200.2", SOPClass.CTPerformedProcedureProtocolStorage}, + {"1.2.840.10008.5.1.4.1.1.481.1", SOPClass.RTImageStorage}, + {"1.2.840.10008.5.1.4.1.1.481.2", SOPClass.RTDoseStorage}, + {"1.2.840.10008.5.1.4.1.1.481.3", SOPClass.RTStructureSetStorage}, + {"1.2.840.10008.5.1.4.1.1.481.4", SOPClass.RTBeamsTreatmentRecordStorage}, + {"1.2.840.10008.5.1.4.1.1.481.5", SOPClass.RTPlanStorage}, + {"1.2.840.10008.5.1.4.1.1.481.6", SOPClass.RTBrachyTreatmentRecordStorage}, + {"1.2.840.10008.5.1.4.1.1.481.7", SOPClass.RTTreatmentSummaryRecordStorage}, + {"1.2.840.10008.5.1.4.1.1.481.8", SOPClass.RTIonPlanStorage}, + {"1.2.840.10008.5.1.4.1.1.481.9", SOPClass.RTIonBeamsTreatmentRecordStorage}, + {"1.2.840.10008.5.1.4.34.7", SOPClass.RTBeamsDeliveryInstructionStorage}, + {"1.2.840.10008.5.1.4.34.10", SOPClass.RTBrachyApplicationSetupDeliveryInstructionStorage}, + }; + + #endregion + + public static SOPClass? SOPClassName(string code) + { + SOPClass? v = null; + _sopCodeMap.TryGetValue(code, out v); + return v; + } + + public static string SOPClassCode(SOPClass sop) + { + string v = null; + _sopNameMap.TryGetValue(sop, out v); + return v; + } + } +} diff --git a/Source/Microsoft.Gateway/.editorconfig b/Source/Microsoft.Gateway/.editorconfig new file mode 100644 index 0000000..2f05df8 --- /dev/null +++ b/Source/Microsoft.Gateway/.editorconfig @@ -0,0 +1,267 @@ +# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference +[*.cs] + + +#Core editorconfig formatting - indentation + +#use soft tabs (spaces) for indentation +indent_style = space + +#Formatting - indentation options + +#indent switch case contents. +csharp_indent_case_contents = true +#csharp_indent_case_contents_when_block +csharp_indent_case_contents_when_block = true +#indent switch labels +csharp_indent_switch_labels = true + +#Formatting - new line options + +#place catch statements on a new line +csharp_new_line_before_catch = true +#place else statements on a new line +csharp_new_line_before_else = true +#require finally statements to be on a new line after the closing brace +csharp_new_line_before_finally = true +#require members of object initializers to be on the same line +csharp_new_line_before_members_in_object_initializers = false +#require braces to be on a new line for methods, lambdas, object_collection_array_initializers, control_blocks, and types (also known as "Allman" style) +csharp_new_line_before_open_brace = methods, lambdas, object_collection_array_initializers, control_blocks, types + +#Formatting - organize using options + +#sort System.* using directives alphabetically, and place them before other usings +dotnet_sort_system_directives_first = true + +#Formatting - spacing options + +#require NO space between a cast and the value +csharp_space_after_cast = false +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_after_colon_in_inheritance_clause = true +#require a space after a keyword in a control flow statement such as a for loop +csharp_space_after_keywords_in_control_flow_statements = true +#require a space before the colon for bases or interfaces in a type declaration +csharp_space_before_colon_in_inheritance_clause = true +#remove space within empty argument list parentheses +csharp_space_between_method_call_empty_parameter_list_parentheses = false +#remove space between method call name and opening parenthesis +csharp_space_between_method_call_name_and_opening_parenthesis = false +#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call +csharp_space_between_method_call_parameter_list_parentheses = false +#remove space within empty parameter list parentheses for a method declaration +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list. +csharp_space_between_method_declaration_parameter_list_parentheses = false + +#Formatting - wrapping options + +#leave code block on single line +csharp_preserve_single_line_blocks = true +#leave statements and member declarations on the same line +csharp_preserve_single_line_statements = true + +#Style - Code block preferences + +#prefer curly braces even for one line of code +csharp_prefer_braces = true:suggestion + +#Style - expression bodied member options + +#prefer block bodies for constructors +csharp_style_expression_bodied_constructors = false:suggestion +#prefer block bodies for methods +csharp_style_expression_bodied_methods = false:suggestion +#prefer expression-bodied members for properties +csharp_style_expression_bodied_properties = true:suggestion + +#Style - expression level options + +#prefer out variables to be declared inline in the argument list of a method call when possible +csharp_style_inlined_variable_declaration = true:suggestion +#prefer tuple names to ItemX properties +dotnet_style_explicit_tuple_names = true:suggestion +#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_member_access = true:suggestion + +#Style - Expression-level preferences + +#prefer default(T) over default +csharp_prefer_simple_default_expression = false:suggestion +#prefer objects to be initialized using object initializers when possible +dotnet_style_object_initializer = true:suggestion +#prefer inferred tuple element names +dotnet_style_prefer_inferred_tuple_names = true:suggestion + +#Style - implicit and explicit types + +#prefer var over explicit type in all cases, unless overridden by another code style rule +csharp_style_var_elsewhere = true:suggestion +#prefer var is used to declare variables with built-in system types such as int +csharp_style_var_for_built_in_types = true:suggestion +#prefer var when the type is already mentioned on the right-hand side of a declaration expression +csharp_style_var_when_type_is_apparent = true:suggestion + +#Style - language keyword and framework type options + +#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +#Style - Miscellaneous preferences + +#prefer anonymous functions over local functions +csharp_style_pattern_local_over_anonymous_function = false:suggestion + +#Style - modifier options + +#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods. +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +#Style - Modifier preferences + +#when this rule is set to a list of modifiers, prefer the specified ordering. +csharp_preferred_modifier_order = public,private,protected,internal,static,readonly,async,override,sealed,virtual,abstract:suggestion + +#Style - Pattern matching + +#prefer pattern matching instead of is expression with type casts +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +#Style - qualification options + +#prefer fields not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_field = false:suggestion +#prefer methods not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_method = false:suggestion +#prefer properties not to be prefaced with this. or Me. in Visual Basic +dotnet_style_qualification_for_property = false:suggestion + +# CA1014: Mark assemblies with CLSCompliant +dotnet_diagnostic.CA1014.severity = none + +# CA1024: Use properties where appropriate +dotnet_diagnostic.CA1024.severity = suggestion + +# CA1031: Do not catch general exception types +dotnet_diagnostic.CA1031.severity = suggestion + +# CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = suggestion + +# CA1052: Static holder types should be Static or NotInheritable +dotnet_diagnostic.CA1052.severity = suggestion + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = suggestion + +# CA1200: Avoid using cref tags with a prefix +dotnet_diagnostic.CA1200.severity = suggestion + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = suggestion + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = suggestion + +# CA1310: Specify StringComparison for correctness +dotnet_diagnostic.CA1310.severity = suggestion + +# CA1508: Avoid dead conditional code +dotnet_diagnostic.CA1508.severity = suggestion + +# CA1711: Identifiers should not have incorrect suffix +dotnet_diagnostic.CA1711.severity = suggestion + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.CA1725.severity = suggestion + +# CA1801: Review unused parameters +dotnet_diagnostic.CA1801.severity = suggestion + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = suggestion + +# CA1812: Avoid uninstantiated internal classes +dotnet_diagnostic.CA1812.severity = suggestion + +# CA1816: Dispose methods should call SuppressFinalize +dotnet_diagnostic.CA1816.severity = suggestion + +# CA1819: Properties should not return arrays +dotnet_diagnostic.CA1819.severity = suggestion + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = suggestion + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = suggestion + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = suggestion + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = suggestion + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = suggestion + +# CA2016: Forward the 'CancellationToken' parameter to methods that take one +dotnet_diagnostic.CA2016.severity = suggestion + +# CA2101: Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA2101.severity = suggestion + +# CA2200: Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = suggestion + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = suggestion + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = suggestion + +# CA2213: Disposable fields should be disposed +dotnet_diagnostic.CA2213.severity = suggestion + +# CA2227: Collection properties should be read only +dotnet_diagnostic.CA2227.severity = suggestion + +# CA2234: Pass system uri objects instead of strings +dotnet_diagnostic.CA2234.severity = suggestion + +# CA5359: Do Not Disable Certificate Validation +dotnet_diagnostic.CA5359.severity = suggestion + +# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes +dotnet_diagnostic.CA5392.severity = suggestion + +# CA5394: Do not use insecure randomness +dotnet_diagnostic.CA5394.severity = suggestion + +# IDE0004: Remove Unnecessary Cast +dotnet_diagnostic.IDE0004.severity = error + +# IDE0005: Using directive is unnecessary. +dotnet_diagnostic.IDE0005.severity = error + +# IDE0035: Remove unreachable code. +dotnet_diagnostic.IDE0035.severity = error + +# IDE0051: Remove unused private members +dotnet_diagnostic.IDE0051.severity = error + +# IDE0052: Remove unread private members +dotnet_diagnostic.IDE0052.severity = error + +# IDE0058: Expression value is never used +dotnet_diagnostic.IDE0058.severity = suggestion + +# IDE0059: Unnecessary assignment of a value +dotnet_diagnostic.IDE0059.severity = suggestion + +# IDE0060: Remove unused parameter +dotnet_diagnostic.IDE0060.severity = suggestion + +# IDE0080: Remove unnecessary suppression operator +dotnet_diagnostic.IDE0080.severity = error diff --git a/Source/Microsoft.Gateway/Microsoft.Gateway.sln b/Source/Microsoft.Gateway/Microsoft.Gateway.sln new file mode 100644 index 0000000..f9ce8d8 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.Gateway.sln @@ -0,0 +1,190 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30704.19 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Listener.Receiver", "Microsoft.InnerEye.Listener.Receiver\Microsoft.InnerEye.Listener.Receiver.csproj", "{67E9B1C1-AF93-46F7-A3F4-70EBAB5E34BD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Listener.Processor", "Microsoft.InnerEye.Listener.Processor\Microsoft.InnerEye.Listener.Processor.csproj", "{163F75D2-4E40-4F9E-A671-AB3EF0E566EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Listener.Common", "Microsoft.InnerEye.Listener.Common\Microsoft.InnerEye.Listener.Common.csproj", "{4BD99139-F4BA-4BBD-A235-6E67048922DF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Listener.Tests", "Microsoft.InnerEye.Listener.Tests\Microsoft.InnerEye.Listener.Tests.csproj", "{FCE746E5-410D-470B-9D0E-601CB3E91DBB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{3D86972C-F841-47E0-8815-10A57A86533D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "IO", "IO", "{5946E473-78AB-45C2-B80D-A471FBF4122D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{C8A127EE-E168-41E9-B9B8-11A3D6FF6FBC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Listener.DataProvider", "Microsoft.InnerEye.DataProvider\Microsoft.InnerEye.Listener.DataProvider.csproj", "{B38F3BB1-48D2-49DF-B6B2-7824AA44A41E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Listener.Tests.Common", "Microsoft.InnerEye.Listener.Tests.Common\Microsoft.InnerEye.Listener.Tests.Common.csproj", "{C4D4B5BC-FB4C-4F24-8D5A-203BE051EB18}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{AC683A70-2A87-428B-9E3B-07963EE5A6D0}" + ProjectSection(SolutionItems) = preProject + NuGet.config = NuGet.config + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installer", "Installer", "{7C71A6F0-75C7-4DA3-AE81-5537FBB45C51}" +EndProject +Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Microsoft.InnerEye.Listener.Wix", "Microsoft.InnerEye.Listener.Wix\Microsoft.InnerEye.Listener.Wix.wixproj", "{10B27116-9B19-4F19-83C0-19AF5AEAF6D3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.InnerEye.Listener.Wix.Actions", "Microsoft.InnerEye.Listener.Wix.Actions\Microsoft.InnerEye.Listener.Wix.Actions.csproj", "{001C5475-43FA-4348-B218-8F12771B56EE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Gateway.MessageQueueing", "Microsoft.InnerEye.Gateway.MessageQueueing\Microsoft.InnerEye.Gateway.MessageQueueing.csproj", "{2C91DE30-0D07-4E72-A69D-5BD34355405B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Gateway.Models", "Microsoft.InnerEye.Gateway.Models\Microsoft.InnerEye.Gateway.Models.csproj", "{49CEFC76-CA31-4E06-AB3E-B5D185780BCB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Gateway.Logging", "Microsoft.InnerEye.Gateway.Logging\Microsoft.InnerEye.Gateway.Logging.csproj", "{6C24CAC3-3890-46BF-8673-02A2FB807025}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DICOMAnonymizer", "..\Anonymizer\DICOMAnonymizer\DICOMAnonymizer.csproj", "{BC82AC8A-90D2-4CAA-8997-AF4EB8359C8B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Anonymizer", "Anonymizer", "{C788D40D-857D-4CBE-95DA-CAFD6829318B}" + ProjectSection(SolutionItems) = preProject + ..\Anonymizer\.editorconfig = ..\Anonymizer\.editorconfig + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SampleConfigurations", "SampleConfigurations", "{F6C3CF4D-C247-4022-A3A0-5D3239840C2D}" + ProjectSection(SolutionItems) = preProject + SampleConfigurations\GatewayProcessorConfig.json = SampleConfigurations\GatewayProcessorConfig.json + SampleConfigurations\GatewayReceiveConfig.json = SampleConfigurations\GatewayReceiveConfig.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Azure.Segmentation.API.Common", "Microsoft.InnerEye.Azure.Segmentation.API.Common\Microsoft.InnerEye.Azure.Segmentation.API.Common.csproj", "{C8E675F4-6E26-404C-A15B-CFF6290D311A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Azure.Segmentation.Client", "Microsoft.InnerEye.Azure.Segmentation.Client\Microsoft.InnerEye.Azure.Segmentation.Client.csproj", "{DEE3A9D7-41C5-4058-8E5E-B913BA71680F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.DicomConstraints", "Microsoft.InnerEye.DicomConstraints\Microsoft.InnerEye.DicomConstraints.csproj", "{DBBE28C8-73E5-4FC8-8616-5803F7FD91E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.DicomConstraints.Tests", "Microsoft.InnerEye.DicomConstraints.Tests\Microsoft.InnerEye.DicomConstraints.Tests.csproj", "{5C075215-DEDB-40DC-8602-68770F9F250C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.InnerEye.Gateway.Sqlite", "Microsoft.InnerEye.Gateway.Sqlite\Microsoft.InnerEye.Gateway.Sqlite.csproj", "{9496C679-2A70-4763-BD9B-54FCEA4E35AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DICOMAnonymizer.Tests", "..\Anonymizer\DICOMAnonymizer.Tests\DICOMAnonymizer.Tests.csproj", "{5B0B84B4-C38C-4F39-B7C2-7F99FDCE8961}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dcmtk", "dcmtk", "{08461B3D-BA7B-4973-ACEE-1F7817381B0D}" + ProjectSection(SolutionItems) = preProject + download_dcmtk.ps1 = download_dcmtk.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{229E1824-806A-446E-B0FC-BDAD3D271C0D}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GatewayModelRulesConfig", "GatewayModelRulesConfig", "{A12EB7AA-47A1-4232-9C88-ED87F243928D}" + ProjectSection(SolutionItems) = preProject + SampleConfigurations\GatewayModelRulesConfig\GatewayModelRulesConfigPassThrough1.json = SampleConfigurations\GatewayModelRulesConfig\GatewayModelRulesConfigPassThrough1.json + SampleConfigurations\GatewayModelRulesConfig\GatewayModelRulesConfigPassThrough2.json = SampleConfigurations\GatewayModelRulesConfig\GatewayModelRulesConfigPassThrough2.json + SampleConfigurations\GatewayModelRulesConfig\GatewayModelRulesConfigPelvis.json = SampleConfigurations\GatewayModelRulesConfig\GatewayModelRulesConfigPelvis.json + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {67E9B1C1-AF93-46F7-A3F4-70EBAB5E34BD}.Debug|x64.ActiveCfg = Debug|x64 + {67E9B1C1-AF93-46F7-A3F4-70EBAB5E34BD}.Debug|x64.Build.0 = Debug|x64 + {67E9B1C1-AF93-46F7-A3F4-70EBAB5E34BD}.Release|x64.ActiveCfg = Release|x64 + {67E9B1C1-AF93-46F7-A3F4-70EBAB5E34BD}.Release|x64.Build.0 = Release|x64 + {163F75D2-4E40-4F9E-A671-AB3EF0E566EB}.Debug|x64.ActiveCfg = Debug|x64 + {163F75D2-4E40-4F9E-A671-AB3EF0E566EB}.Debug|x64.Build.0 = Debug|x64 + {163F75D2-4E40-4F9E-A671-AB3EF0E566EB}.Release|x64.ActiveCfg = Release|x64 + {163F75D2-4E40-4F9E-A671-AB3EF0E566EB}.Release|x64.Build.0 = Release|x64 + {4BD99139-F4BA-4BBD-A235-6E67048922DF}.Debug|x64.ActiveCfg = Debug|x64 + {4BD99139-F4BA-4BBD-A235-6E67048922DF}.Debug|x64.Build.0 = Debug|x64 + {4BD99139-F4BA-4BBD-A235-6E67048922DF}.Release|x64.ActiveCfg = Release|x64 + {4BD99139-F4BA-4BBD-A235-6E67048922DF}.Release|x64.Build.0 = Release|x64 + {FCE746E5-410D-470B-9D0E-601CB3E91DBB}.Debug|x64.ActiveCfg = Debug|x64 + {FCE746E5-410D-470B-9D0E-601CB3E91DBB}.Debug|x64.Build.0 = Debug|x64 + {FCE746E5-410D-470B-9D0E-601CB3E91DBB}.Release|x64.ActiveCfg = Release|x64 + {FCE746E5-410D-470B-9D0E-601CB3E91DBB}.Release|x64.Build.0 = Release|x64 + {B38F3BB1-48D2-49DF-B6B2-7824AA44A41E}.Debug|x64.ActiveCfg = Debug|x64 + {B38F3BB1-48D2-49DF-B6B2-7824AA44A41E}.Debug|x64.Build.0 = Debug|x64 + {B38F3BB1-48D2-49DF-B6B2-7824AA44A41E}.Release|x64.ActiveCfg = Release|x64 + {B38F3BB1-48D2-49DF-B6B2-7824AA44A41E}.Release|x64.Build.0 = Release|x64 + {C4D4B5BC-FB4C-4F24-8D5A-203BE051EB18}.Debug|x64.ActiveCfg = Debug|x64 + {C4D4B5BC-FB4C-4F24-8D5A-203BE051EB18}.Debug|x64.Build.0 = Debug|x64 + {C4D4B5BC-FB4C-4F24-8D5A-203BE051EB18}.Release|x64.ActiveCfg = Release|x64 + {C4D4B5BC-FB4C-4F24-8D5A-203BE051EB18}.Release|x64.Build.0 = Release|x64 + {10B27116-9B19-4F19-83C0-19AF5AEAF6D3}.Debug|x64.ActiveCfg = Debug|x64 + {10B27116-9B19-4F19-83C0-19AF5AEAF6D3}.Debug|x64.Build.0 = Debug|x64 + {10B27116-9B19-4F19-83C0-19AF5AEAF6D3}.Release|x64.ActiveCfg = Release|x64 + {001C5475-43FA-4348-B218-8F12771B56EE}.Debug|x64.ActiveCfg = Debug|x64 + {001C5475-43FA-4348-B218-8F12771B56EE}.Debug|x64.Build.0 = Debug|x64 + {001C5475-43FA-4348-B218-8F12771B56EE}.Release|x64.ActiveCfg = Release|x64 + {001C5475-43FA-4348-B218-8F12771B56EE}.Release|x64.Build.0 = Release|x64 + {2C91DE30-0D07-4E72-A69D-5BD34355405B}.Debug|x64.ActiveCfg = Debug|x64 + {2C91DE30-0D07-4E72-A69D-5BD34355405B}.Debug|x64.Build.0 = Debug|x64 + {2C91DE30-0D07-4E72-A69D-5BD34355405B}.Release|x64.ActiveCfg = Release|x64 + {2C91DE30-0D07-4E72-A69D-5BD34355405B}.Release|x64.Build.0 = Release|x64 + {49CEFC76-CA31-4E06-AB3E-B5D185780BCB}.Debug|x64.ActiveCfg = Debug|x64 + {49CEFC76-CA31-4E06-AB3E-B5D185780BCB}.Debug|x64.Build.0 = Debug|x64 + {49CEFC76-CA31-4E06-AB3E-B5D185780BCB}.Release|x64.ActiveCfg = Release|x64 + {49CEFC76-CA31-4E06-AB3E-B5D185780BCB}.Release|x64.Build.0 = Release|x64 + {6C24CAC3-3890-46BF-8673-02A2FB807025}.Debug|x64.ActiveCfg = Debug|x64 + {6C24CAC3-3890-46BF-8673-02A2FB807025}.Debug|x64.Build.0 = Debug|x64 + {6C24CAC3-3890-46BF-8673-02A2FB807025}.Release|x64.ActiveCfg = Release|x64 + {6C24CAC3-3890-46BF-8673-02A2FB807025}.Release|x64.Build.0 = Release|x64 + {BC82AC8A-90D2-4CAA-8997-AF4EB8359C8B}.Debug|x64.ActiveCfg = Debug|x64 + {BC82AC8A-90D2-4CAA-8997-AF4EB8359C8B}.Debug|x64.Build.0 = Debug|x64 + {BC82AC8A-90D2-4CAA-8997-AF4EB8359C8B}.Release|x64.ActiveCfg = Release|x64 + {BC82AC8A-90D2-4CAA-8997-AF4EB8359C8B}.Release|x64.Build.0 = Release|x64 + {C8E675F4-6E26-404C-A15B-CFF6290D311A}.Debug|x64.ActiveCfg = Debug|x64 + {C8E675F4-6E26-404C-A15B-CFF6290D311A}.Debug|x64.Build.0 = Debug|x64 + {C8E675F4-6E26-404C-A15B-CFF6290D311A}.Release|x64.ActiveCfg = Release|x64 + {C8E675F4-6E26-404C-A15B-CFF6290D311A}.Release|x64.Build.0 = Release|x64 + {DEE3A9D7-41C5-4058-8E5E-B913BA71680F}.Debug|x64.ActiveCfg = Debug|x64 + {DEE3A9D7-41C5-4058-8E5E-B913BA71680F}.Debug|x64.Build.0 = Debug|x64 + {DEE3A9D7-41C5-4058-8E5E-B913BA71680F}.Release|x64.ActiveCfg = Release|x64 + {DEE3A9D7-41C5-4058-8E5E-B913BA71680F}.Release|x64.Build.0 = Release|x64 + {DBBE28C8-73E5-4FC8-8616-5803F7FD91E3}.Debug|x64.ActiveCfg = Debug|x64 + {DBBE28C8-73E5-4FC8-8616-5803F7FD91E3}.Debug|x64.Build.0 = Debug|x64 + {DBBE28C8-73E5-4FC8-8616-5803F7FD91E3}.Release|x64.ActiveCfg = Release|x64 + {DBBE28C8-73E5-4FC8-8616-5803F7FD91E3}.Release|x64.Build.0 = Release|x64 + {5C075215-DEDB-40DC-8602-68770F9F250C}.Debug|x64.ActiveCfg = Debug|x64 + {5C075215-DEDB-40DC-8602-68770F9F250C}.Debug|x64.Build.0 = Debug|x64 + {5C075215-DEDB-40DC-8602-68770F9F250C}.Release|x64.ActiveCfg = Release|x64 + {5C075215-DEDB-40DC-8602-68770F9F250C}.Release|x64.Build.0 = Release|x64 + {9496C679-2A70-4763-BD9B-54FCEA4E35AD}.Debug|x64.ActiveCfg = Debug|x64 + {9496C679-2A70-4763-BD9B-54FCEA4E35AD}.Debug|x64.Build.0 = Debug|x64 + {9496C679-2A70-4763-BD9B-54FCEA4E35AD}.Release|x64.ActiveCfg = Release|x64 + {9496C679-2A70-4763-BD9B-54FCEA4E35AD}.Release|x64.Build.0 = Release|x64 + {5B0B84B4-C38C-4F39-B7C2-7F99FDCE8961}.Debug|x64.ActiveCfg = Debug|x64 + {5B0B84B4-C38C-4F39-B7C2-7F99FDCE8961}.Debug|x64.Build.0 = Debug|x64 + {5B0B84B4-C38C-4F39-B7C2-7F99FDCE8961}.Release|x64.ActiveCfg = Release|x64 + {5B0B84B4-C38C-4F39-B7C2-7F99FDCE8961}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {67E9B1C1-AF93-46F7-A3F4-70EBAB5E34BD} = {3D86972C-F841-47E0-8815-10A57A86533D} + {163F75D2-4E40-4F9E-A671-AB3EF0E566EB} = {3D86972C-F841-47E0-8815-10A57A86533D} + {4BD99139-F4BA-4BBD-A235-6E67048922DF} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {FCE746E5-410D-470B-9D0E-601CB3E91DBB} = {C8A127EE-E168-41E9-B9B8-11A3D6FF6FBC} + {B38F3BB1-48D2-49DF-B6B2-7824AA44A41E} = {5946E473-78AB-45C2-B80D-A471FBF4122D} + {C4D4B5BC-FB4C-4F24-8D5A-203BE051EB18} = {C8A127EE-E168-41E9-B9B8-11A3D6FF6FBC} + {10B27116-9B19-4F19-83C0-19AF5AEAF6D3} = {7C71A6F0-75C7-4DA3-AE81-5537FBB45C51} + {001C5475-43FA-4348-B218-8F12771B56EE} = {7C71A6F0-75C7-4DA3-AE81-5537FBB45C51} + {2C91DE30-0D07-4E72-A69D-5BD34355405B} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {49CEFC76-CA31-4E06-AB3E-B5D185780BCB} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {6C24CAC3-3890-46BF-8673-02A2FB807025} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {BC82AC8A-90D2-4CAA-8997-AF4EB8359C8B} = {C788D40D-857D-4CBE-95DA-CAFD6829318B} + {C8E675F4-6E26-404C-A15B-CFF6290D311A} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {DEE3A9D7-41C5-4058-8E5E-B913BA71680F} = {5946E473-78AB-45C2-B80D-A471FBF4122D} + {DBBE28C8-73E5-4FC8-8616-5803F7FD91E3} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {5C075215-DEDB-40DC-8602-68770F9F250C} = {C8A127EE-E168-41E9-B9B8-11A3D6FF6FBC} + {9496C679-2A70-4763-BD9B-54FCEA4E35AD} = {98DA538C-6CCE-4FE9-84C6-15ADA17DD4A1} + {5B0B84B4-C38C-4F39-B7C2-7F99FDCE8961} = {C788D40D-857D-4CBE-95DA-CAFD6829318B} + {A12EB7AA-47A1-4232-9C88-ED87F243928D} = {F6C3CF4D-C247-4022-A3A0-5D3239840C2D} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D2DF01C6-EB7F-4CA3-B99E-D2546D29E707} + EndGlobalSection +EndGlobal diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ChannelData.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ChannelData.cs new file mode 100644 index 0000000..e0b1114 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ChannelData.cs @@ -0,0 +1,45 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + + using Dicom; + + /// + /// Channel id and files + /// + public class ChannelData + { + /// + /// Initializes a new instance of the class. + /// + /// The channel identifier. + /// The dicom files. + /// + /// channelId + /// or + /// dicomFiles + /// + public ChannelData(string channelID, IEnumerable dicomFiles) + { + ChannelID = channelID ?? throw new ArgumentNullException(nameof(channelID)); + DicomFiles = dicomFiles ?? throw new ArgumentNullException(nameof(dicomFiles)); + } + + /// + /// Gets the channel identifier. + /// + /// + /// The channel identifier. + /// + public string ChannelID { get; } + + /// + /// Gets the dicom files. + /// + /// + /// The dicom files. + /// + public IEnumerable DicomFiles { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfig.cs new file mode 100644 index 0000000..cc4a061 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfig.cs @@ -0,0 +1,126 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// AET configuration + /// + public class AETConfig : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// Type of application entity configuration. + /// The models configuration required if it is not feedback + /// + /// Model configuration null + /// + /// + /// calledAET + /// or + /// modelConfig + /// + [JsonConstructor] + public AETConfig( + AETConfigType aetConfigType, + ModelConstraintsConfig[] modelsConfig) + { + AETConfigType = aetConfigType; + + if (NeedsModelConfig(aetConfigType)) + { + ModelsConfig = modelsConfig ?? throw new ArgumentNullException(nameof(modelsConfig)); + + if (ModelsConfig.Length == 0) + { + throw new ArgumentException("You must specify at least 1 ModelConstraintConfig", nameof(modelsConfig)); + } + } + + // If this is not a model AET config type the models config should be null + if (!NeedsModelConfig(aetConfigType) && modelsConfig != null) + { + throw new ArgumentException("This config type does not require a ModelConstraintsConfig", nameof(modelsConfig)); + } + } + + /// + /// Clone this into a new instance of the class, optionally replacing some properties. + /// + /// Optional new AETConfigType. + /// Optional new ModelConstraintsConfig[]. + /// New AETConfig. + public AETConfig With( + AETConfigType? aetConfigType = null, + ModelConstraintsConfig[] modelsConfig = null) => + new AETConfig( + aetConfigType ?? AETConfigType, + modelsConfig ?? ModelsConfig); + + /// + /// Gets the type of the aet configuration. + /// + /// + /// The type of the aet configuration. + /// + [JsonConverter(typeof(StringEnumConverter))] + public AETConfigType AETConfigType { get; } + + /// + /// If not IsFeedbackAET - then the models configured for this AE, or null otherwise + /// + public ModelConstraintsConfig[] ModelsConfig { get; } + + /// + /// Needs a model configuration + /// + public static bool NeedsModelConfig(AETConfigType aetConfigType) => + aetConfigType == AETConfigType.Model || aetConfigType == AETConfigType.ModelWithResultDryRun; + + /// + /// Configuration needs a destination endpoint + /// + public static bool NeedsEndpoint(AETConfigType aetConfigType) => + aetConfigType == AETConfigType.Model; + + /// + public override bool Equals(object obj) + { + return Equals(obj as AETConfig); + } + + /// + public bool Equals(AETConfig other) + { + return other != null && + AETConfigType == other.AETConfigType && + ((ModelsConfig == null && other.ModelsConfig == null) || + (ModelsConfig != null && ModelsConfig.SequenceEqual(other.ModelsConfig))); + } + + /// + public override int GetHashCode() + { + var hashCode = 602733048; + hashCode = hashCode * -1521134295 + AETConfigType.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ModelsConfig); + return hashCode; + } + + /// + public static bool operator ==(AETConfig left, AETConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(AETConfig left, AETConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfigType.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfigType.cs new file mode 100644 index 0000000..6f3c9c5 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AETConfigType.cs @@ -0,0 +1,23 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + /// + /// Current list of types of Application Entity Titles available in the gateway configuration. + /// + public enum AETConfigType + { + /// + /// The model cloud usage + /// + Model, + + /// + /// The model dry run. It saves the anonymized image to local disk for inspection. + /// + ModelDryRun, + + /// + /// The model with result dry run. It saves the anonymized image and result RT from server to local disk for inspection. + /// + ModelWithResultDryRun, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AnonymisationMethod.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AnonymisationMethod.cs new file mode 100644 index 0000000..32ac395 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/AnonymisationMethod.cs @@ -0,0 +1,24 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + /// + /// The anonymisation method enumeration that defines what type of anonymisation should be applied to a DICOM tag. + /// + public enum AnonymisationMethod + { + /// + /// If we wish to apply a one-way hash to the DICOM tag value. + /// + Hash, + + /// + /// If we wish to keep the DICOM tag value. + /// + Keep, + + /// + /// Randomises the date time DICOM tag with a date value between 0 -> 365 days and a time value + /// between 0 -> 24 hours. + /// + RandomiseDateTime, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ClientAETConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ClientAETConfig.cs new file mode 100644 index 0000000..95ca5a4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ClientAETConfig.cs @@ -0,0 +1,98 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + + /// + /// Encodes configuration information for a DICOM association + /// + public class ClientAETConfig : IEquatable + { + /// + /// Constructs a new ClientAETConfig from the given config options and endpoint specified. + /// + /// Configuration elements for the AET + /// Destination to route results to (may be null) + /// + /// If the channel data for the model needs to be returned with the segmentation result + /// to the destination Dicom end point. + /// + [JsonConstructor] + public ClientAETConfig(AETConfig config, DicomEndPoint destination, bool shouldReturnImage) + { + Config = config ?? throw new ArgumentNullException(nameof(config)); + Destination = destination; + ShouldReturnImage = shouldReturnImage; + } + + /// + /// Clone this into a new instance of the class, optionally replacing some properties. + /// + /// Optional new AETConfig. + /// Optional new destination. + /// Optional new shouldReturnImage. + /// New ClientAETConfig. + public ClientAETConfig With( + AETConfig config = null, + DicomEndPoint destination = null, + bool? shouldReturnImage = null) => + new ClientAETConfig( + config ?? Config, + destination ?? Destination, + shouldReturnImage ?? ShouldReturnImage); + + /// + /// Encodes how an AET is configured + /// + public AETConfig Config { get; } + + /// + /// The destination to route any results generated by models in the above config. + /// May be null. + /// + public DicomEndPoint Destination { get; } + + /// + /// If any image data needs to be returned in the result. + /// + public bool ShouldReturnImage { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ClientAETConfig); + } + + /// + public bool Equals(ClientAETConfig other) + { + return other != null && + EqualityComparer.Default.Equals(Config, other.Config) && + EqualityComparer.Default.Equals(Destination, other.Destination) && + ShouldReturnImage == other.ShouldReturnImage; + } + + /// + public override int GetHashCode() + { + var hashCode = 1850040063; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Config); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Destination); + hashCode = hashCode * -1521134295 + ShouldReturnImage.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(ClientAETConfig left, ClientAETConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ClientAETConfig left, ClientAETConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomEndPoint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomEndPoint.cs new file mode 100644 index 0000000..55a3bf3 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomEndPoint.cs @@ -0,0 +1,92 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + + /// + /// DicomEndPoint + /// + public class DicomEndPoint : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The title. + /// The port. + /// The ip. + /// + /// title + /// or + /// ip + /// + /// The specified Ip is empty - ip + public DicomEndPoint(string title, int port, string ip) + { + Title = title ?? throw new ArgumentNullException(nameof(title)); + Port = port; + Ip = ip ?? throw new ArgumentNullException(nameof(ip)); + if (string.IsNullOrEmpty(Ip)) + { + throw new ArgumentException("The specified Ip is empty", nameof(ip)); + } + } + + /// + /// The DICOM application entity title of the Store-SCP + /// + [Required] + [StringLength(16)] + public string Title { get; } + + /// + /// The port number of the Store-SCP + /// + [Required] + [Range(1, 65535)] + public int Port { get; } + + /// + /// The IP address of the Store-SCP + /// + [Required] + public string Ip { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as DicomEndPoint); + } + + /// + public bool Equals(DicomEndPoint other) + { + return other != null && + Title == other.Title && + Port == other.Port && + Ip == other.Ip; + } + + /// + public override int GetHashCode() + { + var hashCode = 1692922603; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Title); + hashCode = hashCode * -1521134295 + Port.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Ip); + return hashCode; + } + + /// + public static bool operator ==(DicomEndPoint left, DicomEndPoint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(DicomEndPoint left, DicomEndPoint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomTagAnonymisation.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomTagAnonymisation.cs new file mode 100644 index 0000000..8221add --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/DicomTagAnonymisation.cs @@ -0,0 +1,58 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System.ComponentModel.DataAnnotations; + + using Dicom; + + using Microsoft.InnerEye.DicomConstraints; + + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Encodes how a DICOM tag is to be treated within a protocol. + /// + public class DicomTagAnonymisation + { + /// + /// Initializes a new instance of the class. + /// + /// The dicom tag. + /// The anonymisation protocol. + public DicomTagAnonymisation(DicomTag dicomTag, AnonymisationMethod anonymisationProtocol) + : this(new DicomTagIndex(dicomTag), anonymisationProtocol) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The serializable Dicom tag. + /// The anonymisation protocol. + [JsonConstructor] + public DicomTagAnonymisation(DicomTagIndex dicomTagIndex, AnonymisationMethod anonymisationProtocol) + { + DicomTagIndex = dicomTagIndex; + AnonymisationProtocol = anonymisationProtocol; + } + + /// + /// Gets the serializable dicom tag index. + /// + /// + /// The serializable dicom tag index. + /// + [Required] + public DicomTagIndex DicomTagIndex { get; } + + /// + /// Gets the anonymisation protocol that should be used for this DICOM tag. + /// + /// + /// The anonymisation protocol for this DICOM tag. + /// + [Required] + [JsonConverter(typeof(StringEnumConverter))] + public AnonymisationMethod AnonymisationProtocol { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelChannelConstraints.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelChannelConstraints.cs new file mode 100644 index 0000000..4e9dedf --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelChannelConstraints.cs @@ -0,0 +1,118 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + + using Microsoft.InnerEye.DicomConstraints; + + /// + /// Encodes a set of constraints for filtering and accepting a set of dicom datasets as appropriate + /// for use with a particular channel in an ML model. + /// + /// + /// Series data is selected for a model channel using the following process: + /// 1) A subset of images within the series that pass the ImageFilter constraints is selected. + /// 2) The number of filtered images must greater or equal to MinChannelImages and less than or equal to MaxChannelImages + /// 3) All filtered images must pass the ChannelConstraints + /// + public class ModelChannelConstraints : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The channel identifier. + /// The image filter constraints. + /// The channel constraints. + /// The minimum number of channel files required. + /// The maximum number of channel files allowed. + /// + /// channelID + /// or + /// imageFilter + /// or + /// channelConstraints + /// + public ModelChannelConstraints( + string channelID, GroupConstraint imageFilter, GroupConstraint channelConstraints, int minChannelImages, int maxChannelImages) + { + ChannelID = channelID ?? throw new ArgumentNullException(nameof(channelID)); + ImageFilter = imageFilter ?? throw new ArgumentNullException(nameof(imageFilter)); + ChannelConstraints = channelConstraints ?? throw new ArgumentNullException(nameof(channelConstraints)); + MinChannelImages = minChannelImages; + MaxChannelImages = maxChannelImages; + } + + /// + /// The channelID of the model we are constraining + /// + [Required] + public string ChannelID { get; } + + /// + /// Filter in DICOM files before applying the constraints. This selects a subset of images from the same series by + /// filtering unwanted data e.g. extraneous sop classes. + /// + [Required] + public GroupConstraint ImageFilter { get; } + + /// + /// A set of filtered images must pass all constraints to be appropriate for this channel + /// + [Required] + public GroupConstraint ChannelConstraints { get; } + + /// + /// The minimum number of files required for this channel. Use 0 or less to impose no constraint. This is the inclusive + /// lower bound for the number of filtered images. + /// + public int MinChannelImages { get; } + + /// + /// The maximum number of files allowed for this channel. Use 0 or less to impose no maximum constraint. This is the + /// inclusive upper bound for the number of filtered images. + /// + public int MaxChannelImages { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ModelChannelConstraints); + } + + /// + public bool Equals(ModelChannelConstraints other) + { + return other != null && + ChannelID == other.ChannelID && + EqualityComparer.Default.Equals(ImageFilter, other.ImageFilter) && + EqualityComparer.Default.Equals(ChannelConstraints, other.ChannelConstraints) && + MinChannelImages == other.MinChannelImages && + MaxChannelImages == other.MaxChannelImages; + } + + /// + public override int GetHashCode() + { + var hashCode = 1238115311; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ChannelID); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ImageFilter); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ChannelConstraints); + hashCode = hashCode * -1521134295 + MinChannelImages.GetHashCode(); + hashCode = hashCode * -1521134295 + MaxChannelImages.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(ModelChannelConstraints left, ModelChannelConstraints right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ModelChannelConstraints left, ModelChannelConstraints right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelConstraintsConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelConstraintsConfig.cs new file mode 100644 index 0000000..0a7c8a2 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/ModelConstraintsConfig.cs @@ -0,0 +1,94 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Linq; + using Newtonsoft.Json; + + /// + /// Configurable constraints placed on data accepted by a model. + /// + public class ModelConstraintsConfig : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The model identifier. + /// The channel constraints. + /// The tag replacements. + /// + /// channelConstraints + /// or + /// tagReplacements + /// + [JsonConstructor] + public ModelConstraintsConfig( + string modelId, + ModelChannelConstraints[] channelConstraints, + TagReplacement[] tagReplacements) + { + ModelId = modelId; + ChannelConstraints = channelConstraints ?? throw new ArgumentNullException(nameof(channelConstraints)); + TagReplacements = tagReplacements ?? throw new ArgumentNullException(nameof(tagReplacements)); + } + + /// + /// The identifier of the model we are constraining + /// + [Required] + public string ModelId { get; } + + /// + /// Constraints for each channel in the model. + /// + [Required] + public ModelChannelConstraints[] ChannelConstraints { get; } + + /// + /// Gets or sets the TagReplacements + /// + /// + /// The result structure set tags. + /// + [Required] + public TagReplacement[] TagReplacements { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ModelConstraintsConfig); + } + + /// + public bool Equals(ModelConstraintsConfig other) + { + return other != null && + ModelId == other.ModelId && + ChannelConstraints.SequenceEqual(other.ChannelConstraints) && + TagReplacements.SequenceEqual(other.TagReplacements); + } + + /// + public override int GetHashCode() + { + var hashCode = -1007173847; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ModelId); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ChannelConstraints); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TagReplacements); + return hashCode; + } + + /// + public static bool operator ==(ModelConstraintsConfig left, ModelConstraintsConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ModelConstraintsConfig left, ModelConstraintsConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacement.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacement.cs new file mode 100644 index 0000000..e0edda9 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacement.cs @@ -0,0 +1,94 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using Microsoft.InnerEye.DicomConstraints; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// TagReplacement + /// + public class TagReplacement : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The operation. + /// Index of the dicom tag. + /// The value. + /// + /// dicomTagIndex + /// or + /// value + /// + public TagReplacement(TagReplacementOperation operation, DicomTagIndex dicomTagIndex, string value) + { + Operation = operation; + DicomTagIndex = dicomTagIndex ?? throw new ArgumentNullException(nameof(dicomTagIndex)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Gets the operation. + /// + /// + /// The operation. + /// + [JsonConverter(typeof(StringEnumConverter))] + public TagReplacementOperation Operation { get; } + + /// + /// Gets the index of the dicom tag. + /// + /// + /// The index of the dicom tag. + /// + public DicomTagIndex DicomTagIndex { get; } + + /// + /// Gets the value for the operation + /// + /// + /// The value. + /// + public string Value { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as TagReplacement); + } + + /// + public bool Equals(TagReplacement other) + { + return other != null && + Operation == other.Operation && + EqualityComparer.Default.Equals(DicomTagIndex, other.DicomTagIndex) && + Value == other.Value; + } + + /// + public override int GetHashCode() + { + var hashCode = 1049562115; + hashCode = hashCode * -1521134295 + Operation.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DicomTagIndex); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); + return hashCode; + } + + /// + public static bool operator ==(TagReplacement left, TagReplacement right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(TagReplacement left, TagReplacement right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacementOperation.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacementOperation.cs new file mode 100644 index 0000000..e62917e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Gateway/TagReplacementOperation.cs @@ -0,0 +1,18 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + /// + /// TagReplacement operation + /// + public enum TagReplacementOperation + { + /// + /// Update + /// + UpdateIfExists, + + /// + /// The append if exists + /// + AppendIfExists, + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/HashingFunctions.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/HashingFunctions.cs new file mode 100644 index 0000000..353c327 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/HashingFunctions.cs @@ -0,0 +1,36 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System.Security.Cryptography; + using System.Text; + + /// + /// Thread safe hashing function + /// + public static class HashingFunctions + { + /// + /// Hashes the identifier. NOTE SHA512Managed is NOT THREAD SAFE DO NOT MAKE IT STATIC + /// + /// The input. + /// The length. + /// + public static string HashID(string input, int length = 64) + { + using (var hashAlgorithm = new SHA512Managed()) + { + var hashData = hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(input)); + var stringBuilder = new StringBuilder(); + + // This is to to create hashes that are valid UIs, w/o leading zeros. Potential future improvements - different hashing algorithms depending on VR + stringBuilder.Append("1"); + + foreach (var hashedByte in hashData) + { + stringBuilder.Append(hashedByte.ToString("d2")); + } + + return stringBuilder.ToString().Substring(0, length); + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Helpers/DicomCompressionHelpers.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Helpers/DicomCompressionHelpers.cs new file mode 100644 index 0000000..62b7249 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Helpers/DicomCompressionHelpers.cs @@ -0,0 +1,306 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.IO.Compression; + using Dicom; + + /// + /// DICOM compression helpers. + /// + public static class DicomCompressionHelpers + { + /// + /// The channel identifier and dicom series separator. + /// + public const char ChannelIdAndDicomSeriesSeparator = '/'; + + /// + /// The default compression level. + /// + public static readonly CompressionLevel DefaultCompressionLevel = CompressionLevel.Optimal; + + /// + /// Decompresses a collection of ZIP archive entry streams from the compressed stream. + /// + /// The compressed data stream. + /// The decompressed array of streams. + /// If the compressed data stream is null. + public static IReadOnlyList DecompressStreams(Stream compressedData) + { + compressedData = compressedData ?? throw new ArgumentNullException(nameof(compressedData)); + + using (var zipArchive = new ZipArchive(compressedData, ZipArchiveMode.Read, leaveOpen: true)) + { + var result = new List(zipArchive.Entries.Count); + for (var i = 0; i < zipArchive.Entries.Count; i++) + { + result.Add(CreateStream(zipArchive.Entries[i])); + } + + return result; + } + } + + /// + /// Disposes of a collection of streams. + /// + /// The streams collection. + /// The collection of streams is null. + public static void Dispose(this IReadOnlyList streams) + { + streams = streams ?? throw new ArgumentNullException(nameof(streams)); + + foreach (var stream in streams) + { + stream.Dispose(); + } + } + + /// + /// Opens the ZIP archive entry, copies the stream to the provided file stream, and returns a new DICOM file instance. + /// Note: The FO-DICOM DicomFile will not read tags from the stream >64KB. Therefore, to read a tag (such as pixel data) + /// the dicomFileStream must be left open. + /// + /// The ZIP archive entry. + /// The DICOM file stream. + /// The DICOM file. + /// If the zip archive entry or DICOM file stream is null. + public static DicomFile OpenDicomFile(this ZipArchiveEntry zipArchiveEntry, Stream dicomFileStream) + { + zipArchiveEntry = zipArchiveEntry ?? throw new ArgumentNullException(nameof(zipArchiveEntry)); + dicomFileStream = dicomFileStream ?? throw new ArgumentNullException(nameof(dicomFileStream)); + + using (var entryStream = zipArchiveEntry.Open()) + { + entryStream.CopyTo(dicomFileStream); + } + + dicomFileStream.Seek(0, SeekOrigin.Begin); + return DicomFile.Open(dicomFileStream); + } + + /// + /// Creates a new memory stream from a ZIP archive entry. + /// + /// The ZIP archive entry. + /// The new memory stream from the ZIP archive entry. + /// If the zip archive entry is null. + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Resulting stream is returned to caller to dispose.")] + public static Stream CreateStream(this ZipArchiveEntry zipArchiveEntry) + { + zipArchiveEntry = zipArchiveEntry ?? throw new ArgumentNullException(nameof(zipArchiveEntry)); + + var memoryStream = new MemoryStream(); + + try + { + using (var entryStream = zipArchiveEntry.Open()) + { + entryStream.CopyTo(memoryStream); + } + + memoryStream.Seek(0, SeekOrigin.Begin); + + MemoryStream result = memoryStream; + memoryStream = null; + return result; + } + finally + { + memoryStream?.Dispose(); + } + } + + /// + /// Zips and serializes dicom files. + /// + /// The channels. + /// The compression level. + /// + public static byte[] CompressDicomFiles( + IEnumerable channels, + CompressionLevel compressionLevel) + { + channels = channels ?? throw new ArgumentNullException(nameof(channels)); + + using (var memoryStream = new MemoryStream()) + { + using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create)) + { + var fileIndex = 0; + + foreach (var channel in channels) + { + foreach (var file in channel.DicomFiles) + { + var archiveEntry = archive.CreateEntry( + $"{channel.ChannelID}{ChannelIdAndDicomSeriesSeparator}{fileIndex++}", + compressionLevel); + + SaveDicomToArchive(file, archiveEntry); + } + } + } + + return memoryStream.ToArray(); + } + } + + /// + /// Compresses the dicom files to a stream. + /// + /// The stream to compress the DICOM files to. + /// The dicom files. + public static void CompressDicomFilesToStream(Stream stream, params DicomFile[] dicomFiles) + { + CompressDicomFilesToStream(stream, (IEnumerable)dicomFiles); + } + + /// + /// Compresses the dicom files to a stream. + /// + /// The stream to compress the DICOM files to. + /// The dicom files. + public static void CompressDicomFilesToStream(Stream stream, IEnumerable dicomFiles) + { + // Note: we must leave the stream open here as we do not own the stream. + CompressDicomFilesToStream(dicomFiles, stream, DefaultCompressionLevel, leaveStreamOpen: true); + } + + /// + /// Compresses the dicom files to a zip archive and returns a byte array. + /// + /// The dicom files. + /// The compressed Dicom files. + public static byte[] CompressDicomFiles(IEnumerable dicomFiles) + { + return CompressDicomFiles(DefaultCompressionLevel, dicomFiles); + } + + /// + /// Compresses the dicom files to a zip archive and returns a byte array. + /// + /// The dicom files. + /// The compressed Dicom files. + public static byte[] CompressDicomFiles(params DicomFile[] dicomFiles) + { + return CompressDicomFiles(DefaultCompressionLevel, dicomFiles); + } + + /// + /// Compresses the dicom files to a zip archive and returns a byte array. + /// + /// The compression level. + /// The dicom files. + /// The compressed Dicom files. + public static byte[] CompressDicomFiles(CompressionLevel compressionLevel, params DicomFile[] dicomFiles) + { + return CompressDicomFiles(compressionLevel, (IEnumerable)dicomFiles); + } + + /// + /// Compresses the dicom files to a zip archive and returns a byte array. + /// + /// The compression level. + /// The dicom files. + /// The compressed Dicom files. + public static byte[] CompressDicomFiles(CompressionLevel compressionLevel, IEnumerable dicomFiles) + { + dicomFiles = dicomFiles ?? throw new ArgumentNullException(nameof(dicomFiles)); + + using (var memoryStream = new MemoryStream()) + { + CompressDicomFilesToStream(dicomFiles, memoryStream, compressionLevel, leaveStreamOpen: false); + return memoryStream.ToArray(); + } + } + + /// + /// The input byte[] containing zip file contents is deflated. + /// Resulting file names and their contents are returned. + /// + public static IEnumerable<(string FileName, byte[] Data)> DecompressPayload(byte[] data) + { + data = data ?? throw new ArgumentNullException(nameof(data)); + + using (var memoryStream = new MemoryStream(data)) + { + return DecompressPayload(memoryStream); + } + } + + /// + /// The input stream containing zip file contents is deflated. + /// Resulting file names and their contents are returned. + /// + public static IEnumerable<(string FileName, byte[] Data)> DecompressPayload(Stream inputStream) + { + inputStream = inputStream ?? throw new ArgumentNullException(nameof(inputStream)); + + using (var archive = new ZipArchive(inputStream, ZipArchiveMode.Read)) + { + foreach (var archiveEntry in archive.Entries) + { + yield return (archiveEntry.FullName, ToByteArray(archiveEntry)); + } + } + } + + /// + /// Compresses the DICOM files to a stream. + /// + /// The stream. + /// The compression level. + /// If the stream should be left open. + /// The dicom files. + private static void CompressDicomFilesToStream( + IEnumerable dicomFiles, + Stream stream, + CompressionLevel compressionLevel, + bool leaveStreamOpen) + { + using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, leaveStreamOpen)) + { + var fileIndex = 0; + + foreach (var file in dicomFiles) + { + SaveDicomToArchive(file, archive.CreateEntry($"{fileIndex++}", compressionLevel)); + } + } + } + + /// + /// Saves the dicom file to a zip archive entry. + /// + /// The dicom file. + /// The zip archive entry. + private static void SaveDicomToArchive(DicomFile dicomFile, ZipArchiveEntry zipArchiveEntry) + { + using (var entryStream = zipArchiveEntry.Open()) + { + dicomFile.Save(entryStream); + } + } + + /// + /// Read the contents of ziparchiveentry and return them as a byte array. + /// + /// The zip archive entry. + /// The entry contents as a byte array. + private static byte[] ToByteArray(ZipArchiveEntry entry) + { + using (var entryStream = entry.Open()) + { + using (var memoryStream = new MemoryStream()) + { + entryStream.CopyTo(memoryStream); + return memoryStream.ToArray(); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Microsoft.InnerEye.Azure.Segmentation.API.Common.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Microsoft.InnerEye.Azure.Segmentation.API.Common.csproj new file mode 100644 index 0000000..9b6791e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/Microsoft.InnerEye.Azure.Segmentation.API.Common.csproj @@ -0,0 +1,33 @@ + + + net462 + x64 + Microsoft.InnerEye.Azure.Segmentation.API.Common + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Common API code for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ModelResult.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ModelResult.cs new file mode 100644 index 0000000..fa9b575 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.API.Common/ModelResult.cs @@ -0,0 +1,78 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.API.Common +{ + using Dicom; + + using System; + + /// + /// Model result information. + /// + public class ModelResult : IEquatable + { + /// + /// Constructor + /// + /// + /// + /// + public ModelResult(int progress, string error, DicomFile dicomResult) + { + Progress = progress; + Error = error ?? throw new ArgumentNullException(nameof(error)); + DicomResult = dicomResult; + } + + /// + /// Gets or sets progress. + /// + public int Progress { get; } + + /// + /// Gets error message. + /// + public string Error { get; } + + /// + /// Gets the dicom result + /// + public DicomFile DicomResult { get; } + + /// + public bool Equals(ModelResult other) + { + if (other == null) + { + return false; + } + else + { + return Progress == other.Progress && Error == other.Error && DicomResult == other.DicomResult; + } + } + + /// + public override bool Equals(object obj) + { + var progressReport = obj as ModelResult; + + if (progressReport == null) + { + return false; + } + + return Equals(progressReport); + } + + /// + public override string ToString() + { + return $"Progress = {Progress}, Error= {Error}, DicomResult={DicomResult}"; + } + + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/AnonymisationTagHandler.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/AnonymisationTagHandler.cs new file mode 100644 index 0000000..8e1544a --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/AnonymisationTagHandler.cs @@ -0,0 +1,360 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + + using Dicom; + + using DICOMAnonymizer; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + using static DICOMAnonymizer.AnonymizeEngine; + + using AnonFunc = System.Func, Dicom.DicomItem, Dicom.DicomItem>; + + internal class AnonymisationTagHandler : ITagHandler + { + /// + /// The tag handler deidentification name format. + /// + private const string TagHandlerDeidentificationNameFormat = "MS InnerEye {0}: {1}"; + + /// + /// The anonymisation protocol identifier + /// + private readonly Guid _anonymisationProtocolId; + + /// + /// The assembly version. + /// + private readonly string _assemblyVersion; + + /// + /// The anonymisation protocol. + /// + private readonly Dictionary _anonymisationProtocol; + + /// + /// Initializes a new instance of the class. + /// + /// The anonymisation protocol unqiue identifier. + /// The anonymisation protocol. + /// anonymisationProtocol + /// Unknown DICOM anonymisation protocol + public AnonymisationTagHandler(Guid anonymisationProtocolId, IEnumerable anonymisationProtocol) + { + if (anonymisationProtocol == null) + { + throw new ArgumentNullException(nameof(anonymisationProtocol)); + } + + var result = new Dictionary(); + + foreach (var dicomTag in anonymisationProtocol) + { + try + { + switch (dicomTag.AnonymisationProtocol) + { + case AnonymisationMethod.Hash: + result.Add(dicomTag.DicomTagIndex.DicomTag, LongHashID); + break; + case AnonymisationMethod.Keep: + result.Add(dicomTag.DicomTagIndex.DicomTag, KeepItem); + break; + case AnonymisationMethod.RandomiseDateTime: + result.Add(dicomTag.DicomTagIndex.DicomTag, RandomiseDateTime); + break; + default: + throw new ArgumentException("Unknown DICOM anonymisation protocol"); + } + } + catch (ArgumentException e) + { + throw new ArgumentException($"DICOM tag {dicomTag.DicomTagIndex.DicomTag.DictionaryEntry.Name} already added", e); + } + } + + _anonymisationProtocol = result; + _anonymisationProtocolId = anonymisationProtocolId; + _assemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); + } + + /// + /// Gets the deidentification string. + /// + /// + /// The deidentification string. + /// + protected string DeidentificationMethodString => string.Format(TagHandlerDeidentificationNameFormat, _assemblyVersion, _anonymisationProtocolId); + + [Description("Uses SHA512 to generate a new 64 char length Unique Identifier.")] + public static DicomItem LongHashID(DicomDataset oldds, List path, DicomItem item) + { + var el = item as DicomElement; + Trace.Assert(el != null); + + var id = el.Get(); + + if (string.IsNullOrEmpty(id)) + { + return new DicomLongString(item.Tag, string.Empty); + } + + return new DicomLongString(item.Tag, HashingFunctions.HashID(id)); + } + + [Description("Keeps the examined item.")] + public static DicomItem KeepItem(DicomDataset oldds, List path, DicomItem item) => item; + + [Description(@"Adds a random offset of 0-365 days to the original date value & 0-24 hours to the time. + The offset is calculated by seeding a random number generator for the patient ID. + If either the DICOM tag value is empty or the patient ID is empty we result is set to empty. + Note, if this property is a time value type, this randomisation method might not preserve + the file ordering of scans within the same study. If the time anonymisation overflows into + the next day, the day field is stripped and may disrupt the ordering of files aquired on the same day.")] + public static DicomItem RandomiseDateTime(DicomDataset oldds, List path, DicomItem item) + { + var dateTime = TryGetDateTime(oldds, item.Tag); + string patientId; + + oldds.TryGetString(DicomTag.PatientID, out patientId); + + if (!dateTime.HasValue || string.IsNullOrWhiteSpace(patientId)) + { + return GetDicomDateElement(item.ValueRepresentation, item.Tag, null); + } + + dateTime = RandomiseDate(patientId, dateTime.Value); + dateTime = RandomiseTime(patientId, dateTime.Value); + + return GetDicomDateElement(item.ValueRepresentation, item.Tag, dateTime); + } + + /// + /// Example *required* by the Anonymisation Engine + /// + /// + public static List KeepItemExamples() + { + var output = new AnonExample(); + + var value = "M"; + var item = new DicomLongString(DicomTag.PatientSex, value); + output.Input.Add(item + ": " + value); + + var dicomItem = KeepItem(null, null, item); + + AnonExample.InferOutput(item, dicomItem, output); + + return new List { output }; + } + + /// + /// Example *required* by the Anonymisation Engine + /// + /// + public static List LongHashIDExamples() + { + var output1 = new AnonExample(); + + var value = "1.2.34567"; + var item = new DicomUniqueIdentifier(DicomTag.ConcatenationUID, value); + output1.Input.Add(item + ": " + value); + + AnonExample.InferOutput( + item, + LongHashID(null, null, item), + output1); + + var output2 = new AnonExample(); + + value = string.Empty; + item = new DicomUniqueIdentifier(DicomTag.ConcatenationUID, value); + output2.Input.Add(item + ": " + ""); + + AnonExample.InferOutput( + item, + LongHashID(null, null, item), + output2); + + return new List { output1, output2 }; + } + + /// + /// Example *required* by the Anonymisation Engine + /// + /// + public static List RandomiseDateTimeExamples() + { + var dateTime = new DateTime(2018, 2, 15, 3, 20, 58); + var dicomItem = new DicomDateTime(DicomTag.CreationDate, dateTime); + + var dataset = new DicomDataset() + { + { DicomTag.PatientID, "TEST" }, + { dicomItem }, + }; + + var output = new AnonExample(); + output.Input.Add(dicomItem + ": " + dateTime); + + AnonExample.InferOutput(dicomItem, RandomiseDateTime(dataset, null, dicomItem), output); + + return new List { output }; + } + + // TODO refactor into abstract class to avoid all this useless code + public Dictionary GetConfiguration() => null; + + // TODO refactor into abstract class to avoid all this useless code + public Dictionary GetRegexFuncs() => null; + + // TODO refactor into abstract class to avoid all this useless code + public Dictionary GetTagFuncs() => _anonymisationProtocol; + + // TODO refactor into abstract class to avoid all this useless code + public void NextDataset() + { + } + + /// + /// Post-process step for the anonymised DICOM dataset. + /// + /// The DICOM dataset. + public void Postprocess(DicomDataset newds) + { + if (newds == null) + { + throw new ArgumentNullException(nameof(newds)); + } + + var values = new string[] { }; + newds.TryGetValues(DicomTag.DeidentificationMethod, out values); + + var dicomDatasetDeidentificationMethods = values?.ToList() ?? new List(); + + // The VR is LO - therefore chopping this down to 64 characters + dicomDatasetDeidentificationMethods.Add(DeidentificationMethodString.Substring(0, Math.Min(DeidentificationMethodString.Length, 64))); + + newds.AddOrUpdate(DicomTag.DeidentificationMethod, dicomDatasetDeidentificationMethods.ToArray()); + newds.AddOrUpdate(DicomTag.PatientIdentityRemoved, "YES"); + newds.AddOrUpdate(DicomTag.LongitudinalTemporalInformationModified, "MODIFIED"); + } + + /// + /// Tries to get the date time from a DICOM element. + /// + /// A DICOM dataset + /// Tag to fetch from the dataset + /// The date time. + private static DateTime? TryGetDateTime(DicomDataset dicomDataset, DicomTag dicomTag) + { + if (dicomDataset == null || dicomTag == null) + { + return null; + } + + try + { + return dicomDataset.GetSingleValue(dicomTag); + } + + // Tag value null. + catch (DicomDataException) + { + } + + // Tag value is not null but has an invalid format that cannot be parsed into a DateTime object. + catch (FormatException) + { + } + + return null; + } + + /// + /// Randomises the time of a date time property using the patient ID as a random number seed. + /// The date time is randomised by adding a number (between 0 and timeMaximumOffsetInHours * 3600) seconds. + /// Note: This method may not preserve to the continuity of data within the same time period. If this time overflows + /// into the next day, the day field is stripped when saving a DICOM time tag. This will disrupt the ordering of files + /// within a study scanned on the same day. + /// + /// The patient identifier. + /// The date time. + /// The time maximum offset in hours. + /// The randomised date time. + private static DateTime RandomiseTime(string patientId, DateTime dateTime, int timeMaximumOffsetInHours = 24) + { + var random = CreateSeededRandom(patientId); + var secondsOffset = random.Next(0, timeMaximumOffsetInHours * 3600); + + return dateTime.AddSeconds(secondsOffset); + } + + /// + /// Randomises the date of date time property using the patient ID as a random number seed. + /// The date is randomised by adding a number between 0 and dateMaximumOffsetInDays days. + /// + /// The patient identifier. + /// The date time. + /// The date maximum offset in days. + /// The randomised date time. + private static DateTime RandomiseDate(string patientId, DateTime dateTime, int dateMaximumOffsetInDays = 365) + { + var random = CreateSeededRandom(patientId); + var daysOffset = random.Next(0, dateMaximumOffsetInDays); + + try + { + return dateTime.AddDays(daysOffset); + } + catch (ArgumentException) + { + return dateTime.AddDays(-daysOffset); + } + } + + /// + /// Creates a random number generator seeded by the patient ID. + /// + /// The patient identifier. + /// The seeded random number generator. + private static Random CreateSeededRandom(string patientId) + { + return new Random(patientId.Select(x => (int)x).Sum()); + } + + /// + /// Gets the correct DICOM date element for the specified DICOM value representation. + /// + /// The DICOM value representation. + /// The dicom tag. + /// The date time. + /// The correct DICOM date element. + /// If the DICOM value representaiton is not a DICOM date element. + private static DicomDateElement GetDicomDateElement(DicomVR valueRepresentation, DicomTag dicomTag, DateTime? dateTime) + { + if (valueRepresentation == DicomVR.TM) + { + return dateTime.HasValue ? new DicomTime(dicomTag, dateTime.Value) : new DicomTime(dicomTag, string.Empty); + } + else if (valueRepresentation == DicomVR.DA) + { + return dateTime.HasValue ? new DicomDate(dicomTag, dateTime.Value) : new DicomDate(dicomTag, string.Empty); + } + else if (valueRepresentation == DicomVR.DT) + { + return dateTime.HasValue ? new DicomDateTime(dicomTag, dateTime.Value) : new DicomDateTime(dicomTag, string.Empty); + } + + throw new ArgumentException($"Dicom Tag {dicomTag} is of an unknown value representation to anonymise the date time. VR: {valueRepresentation}"); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/ConstraintResult.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/ConstraintResult.cs new file mode 100644 index 0000000..4d15ad4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/ConstraintResult.cs @@ -0,0 +1,112 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System.Collections.Generic; + using System.Linq; + + using Dicom; + + using Microsoft.InnerEye.DicomConstraints; + + /// + /// The constraint result. + /// + public class ConstraintResult + { + /// + /// Initializes a new instance of the class. + /// + /// The dicom constraint results. + public ConstraintResult(IEnumerable dicomConstraintResults) + { + Matched = false; + DicomConstraintResults = dicomConstraintResults; + } + + /// + /// Initializes a new instance of the class. + /// + /// The dicom constraint results. + /// The result. + public ConstraintResult(IEnumerable dicomConstraintResults, T result) + { + Matched = true; + DicomConstraintResults = dicomConstraintResults; + Result = result; + } + + /// + /// Gets a value indicating whether this is matched. + /// + /// + /// true if matched; otherwise, false. + /// + public bool Matched { get; } + + /// + /// Gets the dicom constraint results per Dicom series. + /// + /// + /// The dicom constraint results per Dicom series. + /// + public IEnumerable DicomConstraintResults { get; } + + /// + /// Gets the result. + /// + /// + /// The result. + /// + public T Result { get; } + + /// + /// Gets the Dicom tags from constraint results that have the specified result value. + /// + /// if set to true [constraint result]. + /// The collection of Dicom tags for all constraints the match the result. + public IEnumerable GetDicomConstraintsDicomTags(bool constraintResult = false) + { + return GetDicomConstraintsDicomTags(constraintResult, DicomConstraintResults.ToArray()).Distinct(); + } + + /// + /// Recursively gets all child constraints with the result specified and returns the Dicom tag represented by this constraint. + /// + /// if set to true [constraint result]. + /// The dicom constraint result. + /// The collection of Dicom tags from the constraint results for the specified result type. + private IEnumerable GetDicomConstraintsDicomTags(bool constraintResult, params DicomConstraintResult[] dicomConstraintResult) + { + var result = new List(); + + foreach (var item in dicomConstraintResult) + { + if (item.Result == constraintResult) + { + if (item.ChildResults == null) + { + switch (item.Constraint) + { + case DicomTagConstraint tagConstraint: + { + result.Add(tagConstraint.Index.DicomTag); + break; + } + + case RequiredTagConstraint requiredTag: + { + result.Add(requiredTag.Constraint.Index.DicomTag); + break; + } + } + } + else + { + result.AddRange(GetDicomConstraintsDicomTags(constraintResult, item.ChildResults)); + } + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/IInnerEyeSegmentationClient.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/IInnerEyeSegmentationClient.cs new file mode 100644 index 0000000..098237f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/IInnerEyeSegmentationClient.cs @@ -0,0 +1,72 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + /// + /// Client SDK to talk to InnerEye Cloud Segmentation Service + /// + public interface IInnerEyeSegmentationClient : IDisposable + { + /// + /// Gets the hard-coded anonymisation protocol used for sending anonymising file through the segmentation API. + /// This is not used for Upload as the anonymisation protocol is configurable. + /// + /// + /// The anonymisation protocol used for the segmentation API. + /// + IEnumerable SegmentationAnonymisationProtocol { get; } + + /// + /// Gets the segmentation anonymisation protocol identifier. + /// + /// + /// The segmentation anonymisation protocol identifier. + /// + Guid SegmentationAnonymisationProtocolId { get; } + + /// + /// Checks the client can ping the segmentation service API. + /// + /// A waitable task. + Task PingAsync(); + + /// + /// Anonymizes the dicom file. + /// + /// The dicom file. + /// The anonymisation protocol unqiue identifier. + /// The anonymisation protocol. + /// The anonymized DICOM file. + DicomFile AnonymizeDicomFile(DicomFile dicomFile, Guid anonymisationProtocolId, IEnumerable anonymisationProtocol); + + /// + /// Gets the de-anonymised RT file for a segmentation + /// + /// The model identifier. + /// The segmentation identifier. + /// The reference dicom files. + /// The user replacements for result rt file. + /// + Task SegmentationResultAsync( + string modelId, + string segmentationId, + IEnumerable referenceDicomFiles, + IEnumerable userReplacements); + + /// + /// Creates a task to segment dicom files, it anonymizes the input images + /// + /// Model to use when doing segmentation. + /// DICOM dataset encoding the images as byte[] indexed by AutoSegmentationModel.ChannelIds + /// The segmentationId and the anonymized images + Task<(string segmentationId, IEnumerable postedImages)> StartSegmentationAsync( + string modelId, + IEnumerable channelIdsAndDicomFiles); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/InnerEyeSegmentationClient.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/InnerEyeSegmentationClient.cs new file mode 100644 index 0000000..173a947 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/InnerEyeSegmentationClient.cs @@ -0,0 +1,458 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Security.Authentication; + using System.Threading.Tasks; + + using Dicom; + + using DICOMAnonymizer; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + using static DICOMAnonymizer.AnonymizeEngine; + + /// + /// The InnerEye segmentation client. + /// + /// + public sealed class InnerEyeSegmentationClient : IInnerEyeSegmentationClient + { + /// + /// Default value for the StructureSetLabel DICOM tag in segmentation results + /// + public const string InnerEye = "InnerEye"; + + /// + /// Default value for the SeriesDescription and StructureSetName DICOM tags in segmentation results + /// + public const string NotForClinicalUse = "NOT FOR CLINICAL USE"; + + /// + /// Default value for the Manufacturer DICOM tags in segmentation results + /// + public const string MicrosoftCorp = "Microsoft Corporation"; + + /// + /// Progress value defining if a segmentation task is complete + /// + public const int ProgressCompleted = 100; + + private const string AuthTokenHeaderName = "API_AUTH_SECRET"; + + /// + /// The anonymisation protocol used for any segmentation. If you modify this protocol please update the protocol identifer. + /// + private static readonly IEnumerable _segmentationAnonymisationProtocol = new[] + { + // Geometry + new DicomTagAnonymisation(DicomTag.PatientPosition, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.Columns, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.Rows, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.PixelSpacing, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ImagePositionPatient, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ImageOrientationPatient, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.SliceLocation, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.BodyPartExamined, AnonymisationMethod.Keep), + + // Modality + new DicomTagAnonymisation(DicomTag.Modality, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ModalityLUTSequence, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.HighBit, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.BitsStored, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.BitsAllocated, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.SamplesPerPixel, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.PixelData, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.PhotometricInterpretation, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.PixelRepresentation, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.RescaleIntercept, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.RescaleSlope, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ImageType, AnonymisationMethod.Keep), + + // UIDs + new DicomTagAnonymisation(DicomTag.PatientID, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.SeriesInstanceUID, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.StudyInstanceUID, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.SOPInstanceUID, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.SOPClassUID, AnonymisationMethod.Keep), + + // RT + // RT DicomFrameOfReference + new DicomTagAnonymisation(DicomTag.FrameOfReferenceUID, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.RTReferencedStudySequence, AnonymisationMethod.Keep), + + // RT DicomRTContour + new DicomTagAnonymisation(DicomTag.ReferencedROINumber, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ROIDisplayColor, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ContourSequence, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ROIContourSequence, AnonymisationMethod.Keep), + + // RT DicomRTContourImageItem + new DicomTagAnonymisation(DicomTag.ReferencedSOPClassUID, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ReferencedSOPInstanceUID, AnonymisationMethod.Hash), + + // RT DicomRTContourItem + new DicomTagAnonymisation(DicomTag.NumberOfContourPoints, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ContourData, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ContourGeometricType, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ContourImageSequence, AnonymisationMethod.Keep), + + // RT DicomRTObservation + new DicomTagAnonymisation(DicomTag.RTROIObservationsSequence, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ObservationNumber, AnonymisationMethod.Keep), + + // RT DicomRTReferencedStudy + new DicomTagAnonymisation(DicomTag.RTReferencedSeriesSequence, AnonymisationMethod.Keep), + + // RT DicomRTStructureSet + new DicomTagAnonymisation(DicomTag.StructureSetLabel, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.StructureSetName, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.ReferencedFrameOfReferenceSequence, AnonymisationMethod.Keep), + + // RT DicomRTStructureSetROI + new DicomTagAnonymisation(DicomTag.ROINumber, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ROIName, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.ReferencedFrameOfReferenceUID, AnonymisationMethod.Hash), + new DicomTagAnonymisation(DicomTag.ROIGenerationAlgorithm, AnonymisationMethod.Keep), + new DicomTagAnonymisation(DicomTag.StructureSetROISequence, AnonymisationMethod.Keep), + }; + + private readonly HttpClientHandler _httpClientHandler; + private readonly RetryHandler _retryHandler; + private readonly HttpClient _client; + + /// + /// The de anonymize try add replace at top level + /// + private readonly IEnumerable _deAnonymizeTryAddReplaceAtTopLevel = new[] + { + // Patient module + DicomTag.PatientID, + DicomTag.PatientName, + DicomTag.PatientBirthDate, + DicomTag.PatientSex, + + // Study module + DicomTag.StudyDate, + DicomTag.StudyTime, + DicomTag.ReferringPhysicianName, + DicomTag.StudyID, + DicomTag.AccessionNumber, + DicomTag.StudyDescription, + }; + + /// + /// Initializes a new instance of the class. + /// + /// The base address. + /// The license key environment variable. + public InnerEyeSegmentationClient( + Uri baseAddress, + string licenseKeyEnvVar) + { + HttpClientHandler httpHandler = null; + RetryHandler retryHandler = null; + HttpClient client = null; + + try + { + var licenseKey = Environment.GetEnvironmentVariable(licenseKeyEnvVar) ?? string.Empty; + + var timeOut = TimeSpan.FromMinutes(10); + httpHandler = new HttpClientHandler(); + retryHandler = new RetryHandler(httpHandler); +#pragma warning disable IDE0017 // Simplify object initialization + client = new HttpClient(retryHandler); +#pragma warning restore IDE0017 // Simplify object initialization + + client.BaseAddress = baseAddress; + client.Timeout = timeOut; + client.DefaultRequestHeaders.Add(AuthTokenHeaderName, licenseKey); + + _httpClientHandler = httpHandler; + _retryHandler = retryHandler; + _client = client; + + httpHandler = null; + retryHandler = null; + client = null; + } + finally + { + httpHandler?.Dispose(); + retryHandler?.Dispose(); + client?.Dispose(); + } + } + + /// + public IEnumerable SegmentationAnonymisationProtocol => _segmentationAnonymisationProtocol; + + /// + public Guid SegmentationAnonymisationProtocolId => new Guid("f336816b-4de8-4633-9056-fbe0fe007a03"); + + /// + public DicomFile AnonymizeDicomFile(DicomFile dicomFile, Guid anonymisationProtocolId, IEnumerable anonymisationProtocol) + { + return AnonymizeDicomFile(dicomFile, GetAnonymisationEngine(anonymisationProtocolId, anonymisationProtocol)); + } + + /// + /// Anonymizes the input Dicom files. + /// + /// The Dicom files to anonymize. + /// The anonymisation protocol unqiue identifier. + /// The anonymisation protocol. + /// The anonymized Dicom files. + /// If the Dicom files are null. + /// If any of the Dicom files in the collection are null. + private IEnumerable AnonymizeDicomFiles(IEnumerable dicomFiles, Guid anonymisationProtocolId, IEnumerable anonymisationProtocol) + { + if (dicomFiles == null) + { + throw new ArgumentNullException(nameof(dicomFiles), "The dicom files are null."); + } + + var anonymisationEngine = GetAnonymisationEngine(anonymisationProtocolId, anonymisationProtocol); + + // Anonymize + return dicomFiles.Select(file => AnonymizeDicomFile(file, anonymisationEngine)); + } + + /// + public async Task SegmentationResultAsync( + string modelId, + string segmentationId, + IEnumerable referenceDicomFiles, + IEnumerable userReplacements) + { + var modelResult = await SegmentationResultAsync(modelId, segmentationId); + if (modelResult.DicomResult != null) + { + var anonymizedDicomFile = DeAnonymize( + modelResult.DicomResult, + referenceDicomFiles, + _deAnonymizeTryAddReplaceAtTopLevel, + userReplacements, + SegmentationAnonymisationProtocolId, + SegmentationAnonymisationProtocol); + return new ModelResult(modelResult.Progress, modelResult.Error, anonymizedDicomFile); + } + + return modelResult; + } + + + /// + /// Gets the RT file for a segmentation + /// + /// The model identifier. + /// The segmentation identifier. + /// + private async Task SegmentationResultAsync( + string modelId, + string segmentationId) + { + var response = await _client.GetAsync($@"/v1/model/results/{segmentationId}"); + + if (response.StatusCode == HttpStatusCode.Accepted) + { + var message = await response.Content.ReadAsStringAsync(); + return new ModelResult(50, message, null); + } + else if (response.StatusCode == HttpStatusCode.OK) + { + var zipStream = await response.Content.ReadAsByteArrayAsync(); + using (ZipArchive archive = new ZipArchive(new MemoryStream(zipStream))) + { + if (archive.Entries.Count != 1) + { + throw new NotSupportedException("Only 1 file is supported"); + } + + foreach (ZipArchiveEntry entry in archive.Entries) + { + using (var stream = entry.Open()) + { + var memoryStream = new MemoryStream(); + stream.CopyTo(memoryStream); + memoryStream.Seek(0, SeekOrigin.Begin); + return new ModelResult(100, string.Empty, DicomFile.Open(memoryStream, FileReadOption.ReadAll)); + } + } + } + } + + if (response.StatusCode == HttpStatusCode.NotFound) + { + throw new ArgumentException($"SegmentationId run not found {segmentationId} for model {modelId}"); + } + + throw new Exception(response.ReasonPhrase); + } + + /// + public async Task<(string segmentationId, IEnumerable postedImages)> StartSegmentationAsync( + string modelId, + IEnumerable channelIdsAndDicomFiles) + { + if (channelIdsAndDicomFiles == null) + { + throw new ArgumentNullException(nameof(channelIdsAndDicomFiles), "dicomFiles is null."); + } + + if (channelIdsAndDicomFiles.Any(x => !x.DicomFiles.Any())) + { + throw new ArgumentException(nameof(channelIdsAndDicomFiles), "No dicomFiles in some channelId"); + } + + // Anonymise data + var anonymisedDicomData = channelIdsAndDicomFiles.Select(x => new ChannelData(x.ChannelID, AnonymizeDicomFiles(x.DicomFiles, SegmentationAnonymisationProtocolId, SegmentationAnonymisationProtocol))); + + // Compress anonymised data + var dataZipped = DicomCompressionHelpers.CompressDicomFiles( + channels: anonymisedDicomData, + compressionLevel: DicomCompressionHelpers.DefaultCompressionLevel); + + // POST + var response = await _client.PostAsync($@"/v1/model/start/{modelId}", new ByteArrayContent(dataZipped)); + + if (response.StatusCode.Equals(HttpStatusCode.BadRequest)) + { + throw new ArgumentException(response.ReasonPhrase); + } + + if (!response.IsSuccessStatusCode) + { + throw new Exception(response.ReasonPhrase); + } + + var segmentationId = await response.Content.ReadAsStringAsync(); + Trace.TraceInformation($"Segmentation uploaded with id={segmentationId}"); + var flatAnonymizedDicomFiles = anonymisedDicomData.SelectMany(x => x.DicomFiles); + return (segmentationId, flatAnonymizedDicomFiles); + } + + /// + public async Task PingAsync() + { + try + { + var response = await _client.GetAsync("v1/ping"); + + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + throw new AuthenticationException("Invalid license key"); + } + + response.EnsureSuccessStatusCode(); + } + catch (HttpRequestException reqEx) + { + // Connectivity issue + throw reqEx; + } + } + + /// + /// Dispose diposable objects + /// + public void Dispose() + { + _client.Dispose(); + _retryHandler.Dispose(); + _httpClientHandler.Dispose(); + } + + /// + /// DeAnonymizes an DicomFile and applies user replacements. + /// The replacements are done in this order: + /// 1 - topLevelReplacements + /// 2 - hashed Dicom tags + /// 3 - userReplacements + /// + /// The dicom RT file to be deanonymized + /// The reference dicom files with patient data. At least one file required. + /// The top level patient and study data replacements + /// The user replacements for result rt file + /// The anonymisation protocol unqiue identifier. + /// The anonymisation protocol. + /// The deanonymized RT file deanonymized + public DicomFile DeAnonymize( + DicomFile dicomFile, + IEnumerable referenceDicomFiles, + IEnumerable topLevelReplacements, + IEnumerable userReplacements, + Guid anonymisationProtocolId, + IEnumerable anonymisationProtocol) + { + // Validation + dicomFile = dicomFile ?? throw new ArgumentNullException(nameof(dicomFile)); + referenceDicomFiles = referenceDicomFiles ?? throw new ArgumentNullException(nameof(referenceDicomFiles)); + userReplacements = userReplacements ?? throw new ArgumentNullException(nameof(userReplacements)); + topLevelReplacements = topLevelReplacements ?? throw new ArgumentNullException(nameof(topLevelReplacements)); + + var firstReferenceFile = referenceDicomFiles.First().Dataset; + + foreach (var tagReplacement in topLevelReplacements) + { + TagReplacer.SimpleTopLevelAddOrUpdate(dicomFile.Dataset, firstReferenceFile, tagReplacement); + } + + // Replace all hashed tags + TagReplacer.ReplaceHashedValues( + dicomFile: dicomFile, + referenceDicomFiles: referenceDicomFiles.Select(x => (x, AnonymizeDicomFile(x, anonymisationProtocolId, anonymisationProtocol))), + hashedDicomTags: anonymisationProtocol.Where(x => x.AnonymisationProtocol == AnonymisationMethod.Hash).Select(x => x.DicomTagIndex.DicomTag)); + + foreach (var replacement in userReplacements) + { + TagReplacer.ReplaceUserTag(dicomFile.Dataset, replacement); + } + + return dicomFile; + } + + /// + /// Gets the anonymisation engine for the input protocol. If the input protocol is null we will fallback + /// to using the segmentation service anonymisation protocol. + /// + /// The anonymisation protocol unqiue identifier. + /// The anonymisation protocol. + /// The anonymisation engine. + private static AnonymizeEngine GetAnonymisationEngine(Guid anonymisationProtocolId, IEnumerable anonymisationProtocol) + { + var anonymisationEngine = new AnonymizeEngine(Mode.blank); + var tagHandler = new AnonymisationTagHandler(anonymisationProtocolId, anonymisationProtocol); + + anonymisationEngine.RegisterHandler(tagHandler); + + Trace.TraceInformation(string.Join(Environment.NewLine, anonymisationEngine.ReportRegisteredHandlers())); + + return anonymisationEngine; + } + + /// + /// Anonymizes a single Dicom file. + /// + /// The Dicom file to anonymize. + /// The anonymisation engine. + /// The aonymized Dicom file. + private static DicomFile AnonymizeDicomFile(DicomFile dicomFile, AnonymizeEngine anonymisationEngine) + { + if (dicomFile == null) + { + throw new ArgumentNullException(nameof(dicomFile)); + } + + return anonymisationEngine.Anonymize(dicomFile); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/Microsoft.InnerEye.Azure.Segmentation.Client.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/Microsoft.InnerEye.Azure.Segmentation.Client.csproj new file mode 100644 index 0000000..eeb84b1 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/Microsoft.InnerEye.Azure.Segmentation.Client.csproj @@ -0,0 +1,31 @@ + + + net462 + x64 + Microsoft.InnerEye.Azure.Segmentation.Client + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Segmentation Client for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/RetryHandler.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/RetryHandler.cs new file mode 100644 index 0000000..c1b7624 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/RetryHandler.cs @@ -0,0 +1,78 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System; + using System.Diagnostics; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + /// + /// The retry handler for retrying HTTP client requests + /// + public class RetryHandler : DelegatingHandler + { + /// + /// The maximum number of retries. + /// + private const int MaxRetries = 3; + + /// + /// The delay between each retry in milliseconds. + /// + private const int RetryDelayInMilliseconds = 500; + + /// + /// Initializes a new instance of the class. + /// + /// The inner HTTP message handler. + public RetryHandler(HttpMessageHandler innerHandler) + : base(innerHandler) + { + } + + /// + /// Override for sending a request. + /// + /// The request message. + /// The cancellation token. + /// The HTTP response message or an exception. + protected override async Task SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken) + { + var i = 0; + HttpResponseMessage httpResponseMessage = null; + + while (!cancellationToken.IsCancellationRequested) + { + try + { + httpResponseMessage = await base.SendAsync(request, cancellationToken); + + // Only retry on unknown exceptions or service unvailable. + if (httpResponseMessage.StatusCode != HttpStatusCode.ServiceUnavailable) + { + return httpResponseMessage; + } + } + catch (Exception e) + { + Trace.TraceWarning($"Request failed {request} with exception {e}"); + } + + if (i >= MaxRetries) + { + return httpResponseMessage; + } + + i++; + Trace.TraceWarning($"Retrying {request} retry count = {i}"); + + await Task.Delay(RetryDelayInMilliseconds); + } + + throw new OperationCanceledException(cancellationToken); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/SegmentationModel.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/SegmentationModel.cs new file mode 100644 index 0000000..7b956d7 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/SegmentationModel.cs @@ -0,0 +1,49 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System.Collections.Generic; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + /// + /// The segmentation model. + /// + public class SegmentationModel + { + /// + /// Initializes a new instance of the class. + /// + /// The model identifier. + /// The channel data. + /// The tag replacements. + public SegmentationModel(string modelId, IEnumerable channelData, IEnumerable tagReplacements) + { + ModelId = modelId; + ChannelData = channelData; + TagReplacements = tagReplacements; + } + + /// + /// Gets the model identifier. + /// + /// + /// The model identifier. + /// + public string ModelId { get; } + + /// + /// Gets the channel data. + /// + /// + /// The channel data. + /// + public IEnumerable ChannelData { get; } + + /// + /// Gets the tag replacements. + /// + /// + /// The tag replacements. + /// + public IEnumerable TagReplacements { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/TagReplacer.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/TagReplacer.cs new file mode 100644 index 0000000..2d0ed4e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Azure.Segmentation.Client/TagReplacer.cs @@ -0,0 +1,228 @@ +namespace Microsoft.InnerEye.Azure.Segmentation.Client +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Dicom; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + /// + /// Class for replacing Dicom tags. + /// + public static class TagReplacer + { + /// + /// Applies user DICOM tag replacement rules to a dataset + /// + /// The dicom dataset. + /// The replacement. + public static void ApplyUserReplacement(DicomDataset dicomDataSet, TagReplacement replacement) + { + dicomDataSet = dicomDataSet ?? throw new ArgumentNullException(nameof(dicomDataSet)); + replacement = replacement ?? throw new ArgumentNullException(nameof(replacement)); + + // We must do contains otherwise if the tag is present but empty it will return null + var tag = replacement.DicomTagIndex.DicomTag; + if (dicomDataSet.Contains(tag)) + { + var sourceTagValue = dicomDataSet.GetSingleValueOrDefault(tag, string.Empty); + if (replacement.Operation == TagReplacementOperation.UpdateIfExists) + { + dicomDataSet.AddOrUpdate(tag, replacement.Value); + } + else if (replacement.Operation == TagReplacementOperation.AppendIfExists) + { + dicomDataSet.AddOrUpdate(tag, $"{sourceTagValue}{replacement.Value}"); + } + else + { + throw new InvalidOperationException(nameof(replacement)); + } + } + } + + /// + /// Replaces the user tag. + /// + /// The dicom dataset de anonymized. + /// The tag replacement. + public static void ReplaceUserTag( + DicomDataset dicomDatasetDeAnonymized, + TagReplacement tagReplacement) + { + ApplyUserReplacement(dicomDatasetDeAnonymized, tagReplacement); + + // Handle sequences recursive + foreach (var item in dicomDatasetDeAnonymized) + { + if (item is DicomSequence dicomSequence) + { + foreach (var dataset in dicomSequence.Items) + { + ReplaceUserTag(dataset, tagReplacement); + } + } + } + } + + /// + /// Add or updates a Dicom tag at the top level of Dicom tags. + /// + /// The dicom dataset de anonymized. + /// The reference dicom dataset. + /// The tag to add or update. + public static void SimpleTopLevelAddOrUpdate(DicomDataset dicomDatasetDeAnonymized, DicomDataset referenceDicomDataset, DicomTag addOrUpdateTag) + { + referenceDicomDataset = referenceDicomDataset ?? throw new ArgumentNullException(nameof(referenceDicomDataset)); + dicomDatasetDeAnonymized = dicomDatasetDeAnonymized ?? throw new ArgumentNullException(nameof(dicomDatasetDeAnonymized)); + + var referenceValue = referenceDicomDataset.GetSingleValueOrDefault(addOrUpdateTag, null); + if (referenceValue != null) + { + dicomDatasetDeAnonymized.AddOrUpdate(referenceValue); + } + } + + /// + /// Replaces the hashed values in the anonymised Dicom file. + /// + /// The anonymised Dicom file. + /// The reference dicom files and its associated anonymized representation. + /// The dicom tags that have been hashed. + public static void ReplaceHashedValues( + DicomFile dicomFile, + IEnumerable<(DicomFile Original, DicomFile Anonymized)> referenceDicomFiles, + IEnumerable hashedDicomTags) + { + dicomFile = dicomFile ?? throw new ArgumentNullException(nameof(dicomFile)); + + var hashDictionary = CreateHashDictionary( + referenceDicomFiles.Select(x => (x.Original.Dataset, x.Anonymized.Dataset)), + hashedDicomTags); + + ReplaceHashedValues(dicomFile.Dataset, hashDictionary); + } + + /// + /// Replaces the hashed values in the anonymised Dicom dataset. + /// + /// The dicom dataset. + /// The reference dicom datasets. + /// The hashed dicom tags. + public static void ReplaceHashedValues( + DicomDataset dicomDataset, + IEnumerable<(DicomDataset Original, DicomDataset Anonymized)> referenceDicomDatasets, + IEnumerable hashedDicomTags) + { + var hashDictionary = CreateHashDictionary(referenceDicomDatasets, hashedDicomTags); + + ReplaceHashedValues(dicomDataset, hashDictionary); + } + + /// + /// Replaces the hashed values in the Dicom dataset with its original value. + /// + /// The dicom dataset. + /// The hash dictionary of the hash value as the key and the original value as a value. + /// + /// dicomDataset + /// or + /// hashDictionary + /// + private static void ReplaceHashedValues( + DicomDataset dicomDataset, + Dictionary hashDictionary) + { + if (dicomDataset == null) + { + throw new ArgumentNullException(nameof(dicomDataset)); + } + + if (hashDictionary == null) + { + throw new ArgumentNullException(nameof(hashDictionary)); + } + + foreach (var dicomItem in dicomDataset.ToList()) + { + if (dicomItem is DicomSequence dicomSequence) + { + foreach (var dicomItemDataset in dicomSequence) + { + ReplaceHashedValues(dicomItemDataset, hashDictionary); + } + } + else + { + var hashedValue = dicomDataset.GetSingleValueOrDefault(dicomItem.Tag, string.Empty); + + if (hashDictionary.ContainsKey(hashedValue)) + { + dicomDataset.AddOrUpdate(dicomItem.Tag, hashDictionary[hashedValue]); + } + } + } + } + + /// + /// Creates a dictionary of a hashed value and its original value. + /// + /// The reference dicom datasets. + /// The hashed dicom tags. + /// The hash dictionary. + /// If two values produce the same hash. + /// If any dicom dataset is null or any of the input parameters are null. + private static Dictionary CreateHashDictionary( + IEnumerable<(DicomDataset Original, DicomDataset Anonymized)> referenceDicomDatasets, + IEnumerable hashedDicomTags) + { + if (referenceDicomDatasets == null) + { + throw new ArgumentNullException(nameof(referenceDicomDatasets)); + } + + if (hashedDicomTags == null) + { + throw new ArgumentNullException(nameof(hashedDicomTags)); + } + + var result = new Dictionary(); + + foreach (var (original, anonymised) in referenceDicomDatasets) + { + if (original == null) + { + throw new ArgumentNullException(nameof(referenceDicomDatasets), "null field: Original"); + } + + if (anonymised == null) + { + throw new ArgumentNullException(nameof(referenceDicomDatasets), "null field: Anonymised"); + } + + foreach (var dicomTag in hashedDicomTags) + { + if (original.Contains(dicomTag) && anonymised.Contains(dicomTag)) + { + var hashedValue = anonymised.GetSingleValueOrDefault(dicomTag, string.Empty); + var originalValue = original.GetSingleValueOrDefault(dicomTag, string.Empty); + + if (!result.ContainsKey(hashedValue)) + { + result[hashedValue] = originalValue; + } + else if (result[hashedValue] != originalValue) + { + // This should never happen + throw new ArgumentException($"We have two different values with the same hash. This is not good. Hashed Value: {hashedValue}, Value 1: {result[hashedValue]}, Value 2: {originalValue}"); + } + } + } + } + + return result; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/DicomExtensions.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/DicomExtensions.cs new file mode 100644 index 0000000..ba3be75 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/DicomExtensions.cs @@ -0,0 +1,29 @@ +namespace Microsoft.InnerEye.Listener.DataProvider +{ + using System; + + using Dicom; + + /// + /// Dicom extension methods. + /// + public static class DicomExtensions + { + /// + /// Return true if and only if the DicomDataset has the RT Structure set SOPClassUID + /// + /// The DICOM data set. + /// If the current dataset is an RT structure file. + public static bool IsRTStructure(this DicomDataset dicomDataSet) + { + if (dicomDataSet == null) + { + throw new ArgumentNullException(nameof(dicomDataSet), "The Dicom data set is null"); + } + + return dicomDataSet.GetSingleValueOrDefault( + DicomTag.SOPClassUID, + new DicomUID(string.Empty, string.Empty, DicomUidType.Unknown)) == DicomUID.RTStructureSetStorage; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/DicomDataSender.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/DicomDataSender.cs new file mode 100644 index 0000000..0d9c904 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/DicomDataSender.cs @@ -0,0 +1,233 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Implementations +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.Net.Sockets; + using System.Threading.Tasks; + + using Dicom.Network; + using DicomClient = Dicom.Network.Client.DicomClient; + + using Interfaces; + + using Models; + + /// + /// A wrapper for sending data over Dicom. + /// + public class DicomDataSender : IDicomDataSender + { + /// + /// Initializes a new instance of the class. + /// + public DicomDataSender() + { + } + + /// + /// If the peerApplicationEntity is null. + /// + /// peerApplicationEntity + /// or + /// ownApplicationEntityTitle + /// + public async Task DicomEchoAsync( + string ownApplicationEntityTitle, + string peerApplicationEntityTitle, + int peerApplicationEntityPort, + string peerApplicationEntityIPAddress) + { + // Validate the inputs + ValidateDicomApplicationEntitySettings( + ownApplicationEntityTitle, + peerApplicationEntityTitle, + peerApplicationEntityPort, + peerApplicationEntityIPAddress); + + var result = DicomOperationResult.Error; + var dicomClient = CreateDicomClient( + host: peerApplicationEntityIPAddress, + port: peerApplicationEntityPort, + useTls: false, + callingAe: ownApplicationEntityTitle, + calledAe: peerApplicationEntityTitle); + + await dicomClient.AddRequestAsync(new DicomCEchoRequest + { + OnResponseReceived = ((request, response) => + { + // The Dicom Client send method waits until the response received has finished. + // Do not put any async code in here as it will not wait. + result = GetStatus(response.Status); + }) + }); + + try + { + await dicomClient.SendAsync(); + } + catch (SocketException e) + { + result = DicomOperationResult.NoResponse; + + Trace.TraceError($"[{GetType().Name}] A socket exception occured during the Dicom echo. Failed to get a response. Exception: {e}"); + } + catch (Exception e) + { + Trace.TraceError($"[{GetType().Name}] An unkown exception occured during the Dicom echo. Exception: {e}"); + } + + return result; + } + + /// + /// + /// peerApplicationEntity + /// or + /// ownApplicationEntityTitle + /// + /// If the files are null. + /// If the C-Store request can not communicate with the peer application entity. + public async Task>> SendFilesAsync( + string ownApplicationEntityTitle, + string peerApplicationEntityTitle, + int peerApplicationEntityPort, + string peerApplicationEntityIPAddress, + params Dicom.DicomFile[] dicomFiles) + { + // Validate the inputs + ValidateDicomApplicationEntitySettings( + ownApplicationEntityTitle, + peerApplicationEntityTitle, + peerApplicationEntityPort, + peerApplicationEntityIPAddress); + + if (dicomFiles == null) + { + throw new ArgumentNullException(nameof(dicomFiles)); + } + + var result = new List>(); + + var dicomClient = CreateDicomClient( + host: peerApplicationEntityIPAddress, + port: peerApplicationEntityPort, + useTls: false, + callingAe: ownApplicationEntityTitle, + calledAe: peerApplicationEntityTitle); + + var filesToSend = 0; + + foreach (var dicomFile in dicomFiles) + { + try + { + // Constructor will throw if the Dicom file is invalid + var dicomStoreRequest = new DicomCStoreRequest(dicomFile) + { + // The Dicom Client send method waits until the response received has finished. + // Do not put any async code in here as it will not wait. + OnResponseReceived = (request, response) => + { + Trace.TraceInformation($"[DicomCStoreRequest - OnResponseReceived] Dicom Dataset: {dicomFile} Status: {response.Status}"); + result.Add(Tuple.Create(dicomFile, GetStatus(response.Status))); + }, + }; + + await dicomClient.AddRequestAsync(dicomStoreRequest); + filesToSend++; + } + catch (Exception e) + { + Trace.TraceWarning($"[DicomCStoreRequest] Could not send Dicom Dataset: {dicomFile}. Exception: {e}"); + } + } + + await dicomClient.SendAsync(); + + // Add any missing results + foreach (var dicomFile in dicomFiles) + { + if (result.FirstOrDefault(x => x.Item1 == dicomFile) == null) + { + result.Add(Tuple.Create(dicomFile, DicomOperationResult.Error)); + } + } + + return result; + } + + /// + /// Validates the dicom application entity settings. + /// + /// The own application entity title. + /// The peer application entity title. + /// The peer application entity port. + /// The peer application entity IP address. + /// peerApplicationEntity + /// + /// peerApplicationEntity + /// or + /// ownApplicationEntityTitle + /// + protected static void ValidateDicomApplicationEntitySettings( + string ownApplicationEntityTitle, + string peerApplicationEntityTitle, + int peerApplicationEntityPort, + string peerApplicationEntityIPAddress) + { + var peerApplicationEntityTitleValidationResult = ApplicationEntityValidationHelpers.ValidateTitle(peerApplicationEntityTitle); + var peerApplicationEntityPortValidationResult = ApplicationEntityValidationHelpers.ValidatePort(peerApplicationEntityPort); + var peerApplicationEntityIPAddressValidationResult = ApplicationEntityValidationHelpers.ValidateIPAddress(peerApplicationEntityIPAddress); + + var result = peerApplicationEntityTitleValidationResult && peerApplicationEntityPortValidationResult && peerApplicationEntityIPAddressValidationResult; + + Trace.TraceInformation( + string.Format(CultureInfo.InvariantCulture, "[DicomDataSender] Validation result {0}.", result), + new Dictionary() { + { "PeerApplicationEntityTitle", peerApplicationEntityTitle }, + { "PeerApplicationEntityPort", peerApplicationEntityPort }, + { "PeerApplicationEntityIPAddress", peerApplicationEntityIPAddress }, + { "PeerApplicationEntityTitleValidationResult", peerApplicationEntityTitleValidationResult }, + { "PeerApplicationEntityPortValidationResult", peerApplicationEntityPortValidationResult }, + { "PeerApplicationEntityIpAddressValidationResult", peerApplicationEntityIPAddressValidationResult } + }); + + if (!result) + { + throw new ArgumentException("The peer application entity is invalid.", nameof(peerApplicationEntityTitle)); + } + + if (!ApplicationEntityValidationHelpers.ValidateTitle(ownApplicationEntityTitle)) + { + throw new ArgumentException("The application entity title is invalid.", nameof(ownApplicationEntityTitle)); + } + } + + /// + /// Converts a dicom status to a Dicom operation result. + /// + /// The Dicom status. + /// The Dicom operation result. + private static DicomOperationResult GetStatus(DicomStatus status) + { + // On any warning we still return success. Please check here for a list of warnings: https://fo-dicom.github.io/html/f270d490-66d6-28d5-1fa3-f619b4792034.htm + return status == DicomStatus.Success || + status == DicomStatus.StorageCoercionOfDataElements || + status == DicomStatus.StorageElementsDiscarded + ? DicomOperationResult.Success : DicomOperationResult.Error; + } + + /// + /// Gets a Dicom client. + /// + /// The Dicom client. + private static DicomClient CreateDicomClient(string host, int port, bool useTls, string callingAe, string calledAe) + { + return new DicomClient(host, port, useTls, callingAe, calledAe); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDataReceiver.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDataReceiver.cs new file mode 100644 index 0000000..b25f469 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDataReceiver.cs @@ -0,0 +1,170 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Implementations +{ + using System; + using System.Collections.Generic; + using System.Threading; + + using DataProvider.Interfaces; + using DataProvider.Models; + + using global::Dicom; + using global::Dicom.Network; + + /// + /// Dicom data receiver implementation. + /// + public class ListenerDataReceiver : IDicomDataReceiver + { + /// + /// The Dicom saver. + /// + private readonly IDicomSaver _dicomSaver; + + /// + /// The Dicom server. + /// + private IDicomServer _dicomServer; + + /// + /// If this instance is disposed. + /// + private bool _isDisposed = false; + + /// + /// Constructor. This class is disposable. + /// + /// The Dicom saver. + public ListenerDataReceiver(IDicomSaver dicomSaver) + { + _dicomSaver = dicomSaver; + } + + /// + /// Gets if the server is currently listening. + /// + public bool IsListening => _dicomServer?.IsListening ?? false; + + /// + /// Data received event - this can be called from multiple different threads. + /// + public event EventHandler DataReceived; + + /// + /// If the service is already listening. + /// If another service is already listening on this socket. + public bool StartServer(int port, Func> getAcceptedTransferSyntaxes, TimeSpan timeout) + { + if (!ApplicationEntityValidationHelpers.ValidatePort(port)) + { + throw new ArgumentException("The port is not valid.", nameof(port)); + } + + // Check if we are already listening + if (IsListening) + { + throw new DicomNetworkException("We are already listening. Please call stop server before starting."); + } + + // Looks like StartServer has been called before but failed to start listening. + // Lets dispose of the current instance and try again. + if (_dicomServer != null) + { + DisposeDicomServer(); + } + + var fileStoreParameters = new DicomFileStoreParameters(DicomDataReceiverUpdate, getAcceptedTransferSyntaxes, _dicomSaver); + + // Preload dictionary to prevent timeouts + DicomDictionary.EnsureDefaultDictionariesLoaded(); + + // Constructing the listener dicom server will attempt to start the service. + _dicomServer = DicomServer.Create(ipAddress: "localhost", port: port, userState: fileStoreParameters); + + // Wait until listening or we have an exception. + SpinWait.SpinUntil(() => _dicomServer.IsListening || _dicomServer.Exception != null, timeout); + + if (_dicomServer.Exception != null) + { + // Throw any exceptions + throw _dicomServer.Exception; + } + + return _dicomServer?.IsListening ?? false; + } + + /// + /// Stops the server. + /// + public void StopServer() + { + DisposeDicomServer(); + } + + /// + /// Implements the disposable pattern. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of this instance by calling stop server. + /// + /// If we are current disposing. + protected virtual void Dispose(bool disposing) + { + if (_isDisposed) + { + return; + } + + if (disposing) + { + DisposeDicomServer(); + } + + _isDisposed = true; + } + + /// + /// Disposes of the current dicom server instance. + /// + private void DisposeDicomServer() + { + if (_dicomServer != null) + { + _dicomServer.Stop(); + //_dicomServer.BackgroundWorker.Wait(); + + _dicomServer.Dispose(); + + _dicomServer = null; + } + } + + /// + /// Data receiver update method. + /// + /// The identifier for this association. + /// The date time the socket connection started.. + /// The Dicom association object. + /// The progress code. + private void DicomDataReceiverUpdate( + Guid associationId, + DateTime socketConnectionDateTime, + DicomAssociation dicomAssociation, + DicomReceiveProgressCode progressCode) + { + DataReceived?.Invoke( + this, + new DicomDataReceiverProgressEventArgs( + dicomSaver: _dicomSaver, + progressCode: progressCode, + socketConnectionDateTime: socketConnectionDateTime, + dicomAssociation: dicomAssociation, + associationId: associationId)); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomSaver.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomSaver.cs new file mode 100644 index 0000000..b13782e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomSaver.cs @@ -0,0 +1,225 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Implementations +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security; + + using global::Dicom; + using global::Dicom.Network; + + using Microsoft.InnerEye.Listener.DataProvider.Interfaces; + using Microsoft.InnerEye.Listener.DataProvider.Models; + + /// + /// Dicom saver implementation for saving files to folders based on the association unique ID. + /// + public class ListenerDicomSaver : IDicomSaver + { + /// + /// Lock object for locking when creating folders. + /// + private readonly object _lockObject = new object(); + + /// + /// Constructor - this will throw exceptions if we cannot get or create the root folder + /// or we do not have write access to the root folder. + /// + /// The root folder for saving images. + /// If the root folder is null or white space.. + /// If we do not have permissions to create the root folder or sub folders. + public ListenerDicomSaver(string rootFolder) + { + if (string.IsNullOrWhiteSpace(rootFolder)) + { + throw new ArgumentException("The root folder is null or white space.", nameof(rootFolder)); + } + + var directoryInformation = GetOrCreateDirectory(rootFolder); + + if (!directoryInformation.Exists) + { + throw new SecurityException(FormatLogStatement("The directory does not exist or could not be created.")); + } + + RootSaveFolder = directoryInformation; + + // Attempt to create a folder in the root folder. + // If we do not have permission the test directory will not be created. + var testDirectoryInformation = GetOrCreateSaveFolder(Guid.Empty); + + if (!testDirectoryInformation.Exists) + { + throw new SecurityException( + FormatLogStatement(string.Format( + CultureInfo.InvariantCulture, + "The process does not have the required permissions to create folders in {0}.", + rootFolder))); + } + + testDirectoryInformation.Delete(); + } + + /// + /// Gets the root folder where all data will be saved. + /// + /// + /// The root folder where all data will be saved. + /// + public DirectoryInfo RootSaveFolder { get; } + + /// + /// Gets the folder path where images will be saved using the supplied directory name. + /// + /// The association identifier. + /// The save folder path. + public string GetSaveFolderPath(Guid associationId) => Path.Combine(RootSaveFolder.FullName, associationId.ToString()); + + /// + /// Checks if any data has been received for the specific association identifier. + /// + /// The association identifier. + /// True if any data has been received for this association. + public bool CheckIfAnyDataReceived(Guid associationId) + { + var directoryInfo = new DirectoryInfo(GetSaveFolderPath(associationId)); + + return directoryInfo.Exists && directoryInfo.EnumerateFiles().Any(); + } + + /// + /// This method saves incoming DICOM image according to implementation's logic. + /// DicomStoreException thrown from this method will return its Status to the SCU and log the status. + /// Generic exceptions thrown from this method will return DicomStatus.ProcessingFailure to the SCU. + /// + /// The association unique ID. + /// The Dicom C-Store request. + /// File to save + /// The file path where the image was saved. + public string SaveDicom(Guid associationId, DicomCStoreRequest request, DicomFile dicomFile) + { + if (dicomFile == null || dicomFile.Dataset == null) + { + throw new ArgumentNullException(nameof(dicomFile), FormatLogStatement("This Dicom file or dataset cannot be null.")); + } + + if (request == null) + { + throw new ArgumentNullException(nameof(request)); + } + + var sopInstanceUID = request.SOPInstanceUID.UID; + + if (string.IsNullOrWhiteSpace(sopInstanceUID)) + { + throw new DicomStoreException( + DicomStatus.MissingAttributeValue, + FormatLogStatement("We cannot save a file without a SOP instance UID")); + } + + var saveFolder = GetOrCreateSaveFolder(associationId); + + if (!saveFolder.Exists) + { + // This will never be a permissions failure as this is checked when this class is constructed. + // If we fail this will be because we have run out of space on disk. + throw new DicomStoreException( + DicomStatus.StorageStorageOutOfResources, + FormatLogStatement(string.Format( + CultureInfo.InvariantCulture, + "An exception occurred trying to get or create the folder path {0}", + saveFolder))); + } + + var saveFilePath = Path.Combine(saveFolder.FullName, string.Format(CultureInfo.InvariantCulture, "{0}.dcm", sopInstanceUID)); + + try + { + dicomFile.Save(saveFilePath); + } + catch (Exception e) + { + Trace.TraceError(FormatLogStatement(string.Format( + CultureInfo.InvariantCulture, + "Failed to save the Dicom file to {0} with exception {1}", + saveFilePath, + e))); + + throw new DicomStoreException(DicomStatus.ProcessingFailure, e.ToString()); + } + + return saveFilePath; + } + + /// + /// Attempts to get or create the save folder for the association identifier. + /// + /// The association identifier. + /// The save folder directory information. + private DirectoryInfo GetOrCreateSaveFolder(Guid associationId) + { + var saveFolderPath = GetSaveFolderPath(associationId); + return GetOrCreateDirectory(saveFolderPath); + } + + /// + /// Attempts to get or create (if the folder does not exist) the specified folder path. + /// + /// The folder path to get or create. + /// The folder directory information. + private DirectoryInfo GetOrCreateDirectory(string folderPath) + { + if (string.IsNullOrWhiteSpace(folderPath)) + { + throw new ArgumentException("The folder path is null or white space.", nameof(folderPath)); + } + + return GetOrCreateDirectory(new DirectoryInfo(folderPath)); + } + + /// + /// Attempts to get or create a folder (if the folder does not exist) using the input directory information. + /// + /// The directory information for the folder to get or create. + /// The directory information. + private DirectoryInfo GetOrCreateDirectory(DirectoryInfo directoryInfo) + { + if (directoryInfo == null) + { + throw new ArgumentNullException(nameof(directoryInfo)); + } + + try + { + // We create directories under lock, as we may have multiple events trying to save at the same time + lock (_lockObject) + { + if (!directoryInfo.Exists) + { + directoryInfo = Directory.CreateDirectory(directoryInfo.FullName); + } + } + } + catch (Exception e) + { + Trace.TraceError(FormatLogStatement(string.Format( + CultureInfo.InvariantCulture, + "Failed to create directory {0} with exception {1}", + directoryInfo.FullName, + e))); + } + + return directoryInfo; + } + + /// + /// Gets for formatted statement for logging. + /// + /// The inner statement. + /// The formatted log statement. + private string FormatLogStatement(string value) => + string.Format(CultureInfo.InvariantCulture, "[{0}] {1}", GetType().Name, value); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomService.cs new file mode 100644 index 0000000..ef38837 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Implementations/ListenerDicomService.cs @@ -0,0 +1,275 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Implementations +{ + using System; + using System.Diagnostics; + using System.Globalization; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Dicom; + using Dicom.Log; + using Dicom.Network; + + using Models; + + /// + /// The listener dicom service. + /// + public class ListenerDicomService : DicomService, IDicomServiceProvider, IDicomCStoreProvider, IDicomCEchoProvider + { + /// + /// The current transfer ID. + /// + private readonly Guid _transferId; + + /// + /// The date time of the socket connection. + /// + private readonly DateTime _socketConnectionDateTime; + + /// + /// The current Dicom association. + /// + private DicomAssociation _currentDicomAssociation; + + /// + /// Parameters that contain how we communicate back with the listener and save files. + /// + private DicomFileStoreParameters _parameters; + + /// + /// Default fo-dicom constructor. + /// + /// The network stream. + /// The fallback encoding. + /// The logger. + public ListenerDicomService(INetworkStream stream, Encoding fallbackEncoding, Logger log) + : base(stream, fallbackEncoding, log) + { + _transferId = Guid.NewGuid(); + _socketConnectionDateTime = DateTime.UtcNow; + } + + /// + /// Called from within fo-dicom when we receive an association request. + /// + /// The association details. + public Task OnReceiveAssociationRequestAsync(DicomAssociation association) + { + var parameters = GetParameters(); + + if (parameters == null) + { + Trace.TraceError(FormatLogStatement("How did this happen? The parameters are null.")); + + // Permanent reject as this should not happen and we cannot recover from this + return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceProviderACSE, DicomRejectReason.NoReasonGiven); + } + + // This should not happen otherwise something has gone wrong. + if (_currentDicomAssociation != null) + { + Trace.TraceError(FormatLogStatement("Our understanding of the FO-Dicom Listener Dicom Service is incorrect. We have received multiple association requests in the same object instance.")); + + return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceProviderACSE, DicomRejectReason.TemporaryCongestion); ; + } + + if (association == null) + { + Trace.TraceError(FormatLogStatement("Received association with a null Dicom association object.")); + + return SendAssociationRejectAsync(DicomRejectResult.Permanent, DicomRejectSource.ServiceProviderACSE, DicomRejectReason.NoReasonGiven); ; + } + + _currentDicomAssociation = association; + + Trace.TraceInformation(FormatLogStatement("Receive association requested.")); + + var serviceProtocols = parameters.GetAcceptedTransferSyntaxes(); + + // Filter by supported PC's + foreach (var presentationContext in association.PresentationContexts) + { + if (serviceProtocols.ContainsKey(presentationContext.AbstractSyntax)) + { + if (!presentationContext.AcceptTransferSyntaxes(serviceProtocols[presentationContext.AbstractSyntax], true)) + { + Trace.TraceInformation(FormatLogStatement("Presentation Context rejected: no supported transfer syntaxes.")); + } + } + else + { + Trace.TraceInformation(FormatLogStatement( + string.Format( + CultureInfo.InvariantCulture, + "Association requested unsupported SOP class: {0}", + presentationContext.AbstractSyntax))); + + presentationContext.AcceptTransferSyntaxes(new DicomTransferSyntax[0], true); + } + } + + parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.AssociationEstablished); + + return SendAssociationAcceptAsync(association); + } + + /// + /// Called from within fo-dicom when we want to release the association. + /// + public Task OnReceiveAssociationReleaseRequestAsync() + { + Trace.TraceInformation(FormatLogStatement("Receive association release requested.")); + + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.AssociationReleased); + + return SendAssociationReleaseResponseAsync(); + } + + /// + /// Called from within fo-dicom when a receive is aborted. + /// + /// The abort source. + /// The abort reason. + public void OnReceiveAbort(DicomAbortSource source, DicomAbortReason reason) + { + Trace.TraceInformation(FormatLogStatement( + string.Format( + CultureInfo.InvariantCulture, + "{0} aborted the receive with reason {1}.", + source, + reason))); + + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.TransferAborted); + } + + /// + /// Called from within fo-dicom when a connection is closed. + /// + /// If this was a forced connection closed this will contain the exception details. + public void OnConnectionClosed(Exception exception) + { + Trace.TraceInformation(FormatLogStatement("Connection closed.")); + + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.ConnectionClosed); + } + + /// + /// Called from within fo-dicom when a C-Store is requested. + /// + /// The C-Store request details. + /// Our response depending on whether we managed to save the incoming data. + public DicomCStoreResponse OnCStoreRequest(DicomCStoreRequest request) + { + if (request == null) + { + throw new ArgumentNullException(nameof(request), "The request should never be null, but it is."); + } + + Trace.TraceInformation(FormatLogStatement("C-Store request.")); + + // Save the file to disk and get the file path for where it is saved + try + { + _parameters.DicomSaver.SaveDicom(_transferId, request, request.File); + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.FileReceived); + + Trace.TraceInformation(FormatLogStatement(string.Format(CultureInfo.InvariantCulture, "Received {0}.", request.SOPInstanceUID.UID))); + + return new DicomCStoreResponse(request, DicomStatus.Success); + } + + catch (DicomStoreException storeException) + { + var progressCode = DicomReceiveProgressCode.ErrorSavingFile; + + if (storeException.Status == DicomStatus.StorageCannotUnderstand) + { + progressCode = DicomReceiveProgressCode.ErrorCouldNotUnderstand; + } + if (storeException.Status == DicomStatus.StorageStorageOutOfResources) + { + progressCode = DicomReceiveProgressCode.GenericStorageException; + } + + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, progressCode); + + Trace.TraceError(storeException.Message); + + return new DicomCStoreResponse(request, storeException.Status); + } + + catch (Exception e) + { + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.ErrorSavingFile); + + Trace.TraceError(e.Message); + + return new DicomCStoreResponse(request, DicomStatus.ProcessingFailure); + } + } + + /// + /// Called from within fo-dicom is the SopInstance could not be parsed. + /// + /// The temporary file name. + /// The exception detail. + public void OnCStoreRequestException(string tempFileName, Exception e) + { + Trace.TraceError(FormatLogStatement(string.Format(CultureInfo.InvariantCulture, "C-Store exception {0}.", e?.Message))); + + // Note that fo-dicom sends an error response here and continues to listen... + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.TransferAborted); + } + + /// + /// Called from within fo-dicom when a C-Echo is requested. + /// + /// The request details. + /// A success response. + public DicomCEchoResponse OnCEchoRequest(DicomCEchoRequest request) + { + Trace.TraceInformation(FormatLogStatement("C-Store echo request.")); + + _parameters.Update?.Invoke(_transferId, _socketConnectionDateTime, _currentDicomAssociation, DicomReceiveProgressCode.Echo); + + return new DicomCEchoResponse(request, DicomStatus.Success); + } + + /// + /// Gets the parameters. + /// FO-dicom server initialization does not appear to be thread safe so we use this code to give it ample time to init. + /// Please access the parameters by calling GetParameters() in an association request. + /// + /// The time we will wait until the parameters are not null. + /// The parameters. + private DicomFileStoreParameters GetParameters(uint timeoutSeconds = 5) + { + _parameters = UserState as DicomFileStoreParameters; + + if (_parameters == null && timeoutSeconds > 0) + { + SpinWait.SpinUntil(() => _parameters != null, TimeSpan.FromSeconds(timeoutSeconds)); + } + + if (_parameters == null) + { + Trace.TraceError("No FileStoreParameters object provided at DicomServer initialization. Incoming DICOM requests will not be handled correctly."); + } + else if (_parameters?.DicomSaver == null) + { + Trace.TraceWarning("File Store service is created, but no image saver class provided. Incoming images will not be saved."); + } + + return _parameters; + } + + /// + /// Gets for formatted statement for logging. + /// + /// The inner statement. + /// The formatted log statement. + private string FormatLogStatement(string value) => + string.Format(CultureInfo.InvariantCulture, "[Transfer:{0}] {1}", _transferId, value); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataReceiver.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataReceiver.cs new file mode 100644 index 0000000..1b81bb6 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataReceiver.cs @@ -0,0 +1,47 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Interfaces +{ + using System; + using System.Collections.Generic; + + using Dicom; + + using Models; + + /// + /// The Dicom server creator. + /// + public interface IDicomDataReceiver : IDisposable + { + /// + /// Gets if the server is currently listening. + /// + bool IsListening { get; } + + /// + /// Data received event - this can be called from multiple different threads. + /// + event EventHandler DataReceived; + + /// + /// Starts the Dicom server using the supplied dicom application entity. + /// + /// + /// The port to start listening on. + /// Note, currently we always listens on Ipv4.Any and do not use DcmOwnAe.Title or DcmOwnAe.IpAddress. + /// + /// + /// The function for getting the accepted transfer syntaxes when an association is made. + /// + /// + /// The time we will wait for the server to start listening. + /// Recommended timeout is 2 seconds. + /// + /// If we are listening. + bool StartServer(int port, Func> getAcceptedTransferSyntaxes, TimeSpan timeout); + + /// + /// Stops the server. + /// + void StopServer(); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataSender.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataSender.cs new file mode 100644 index 0000000..25e1343 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomDataSender.cs @@ -0,0 +1,52 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Interfaces +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Listener.DataProvider.Models; + + /// + /// Dicom data sender interface. + /// + public interface IDicomDataSender + { + /// + /// Sends an echo request to the peer application entity. + /// + /// Our own application entity title. + /// The peer application entity title. + /// The peer application entity port. + /// The peer application entity IP address. + /// If the peerApplicationEntity is null. + /// + /// peerApplicationEntity + /// or + /// ownApplicationEntityTitle + /// + /// The Dicom operation result. + Task DicomEchoAsync( + string ownApplicationEntityTitle, + string peerApplicationEntityTitle, + int peerApplicationEntityPort, + string peerApplicationEntityIPAddress); + + /// + /// Sends the Dicom files as a Dicom C-Store request. + /// + /// The own application entity title. + /// The peer application entity title. + /// The peer application entity port. + /// The peer application entity IP address. + /// The Dicom files to send. + /// The collection of Dicom files and the Dicom operation result for each item. + Task>> SendFilesAsync( + string ownApplicationEntityTitle, + string peerApplicationEntityTitle, + int peerApplicationEntityPort, + string peerApplicationEntityIPAddress, + params DicomFile[] dicomFiles); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomSaver.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomSaver.cs new file mode 100644 index 0000000..7c8aa3b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Interfaces/IDicomSaver.cs @@ -0,0 +1,47 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Interfaces +{ + using System; + using System.IO; + + using Dicom; + using Dicom.Network; + + /// + /// Interface for Dicom saving. + /// + public interface IDicomSaver + { + /// + /// Gets the root folder where all data will be saved. + /// + /// + /// The root folder where all data will be saved. + /// + DirectoryInfo RootSaveFolder { get; } + + /// + /// Gets the folder path where images will be saved using the supplied directory name. + /// + /// The association identifier. + /// The save folder path. + string GetSaveFolderPath(Guid associationId); + + /// + /// Checks if any data has been received for the specific association. + /// + /// The association identifier. + /// True if any data has been received for the association. + bool CheckIfAnyDataReceived(Guid associationId); + + /// + /// This method saves incoming DICOM data according to implementation's logic. + /// DicomStoreException thrown from this method will return its Status to the SCU and log the status. + /// Generic exceptions thrown from this method will return DicomStatus.ProcessingFailure to the SCU. + /// + /// The association unique ID. + /// The Dicom C-Store request. + /// File to save + /// The file path where the image was saved. + string SaveDicom(Guid associationId, DicomCStoreRequest request, DicomFile dicomFile); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Microsoft.InnerEye.Listener.DataProvider.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Microsoft.InnerEye.Listener.DataProvider.csproj new file mode 100644 index 0000000..994e39e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Microsoft.InnerEye.Listener.DataProvider.csproj @@ -0,0 +1,25 @@ + + + net462 + x64 + Microsoft.InnerEye.Listener.DataProvider + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + DICOM Data provider for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/ApplicationEntityValidationHelpers.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/ApplicationEntityValidationHelpers.cs new file mode 100644 index 0000000..f5665db --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/ApplicationEntityValidationHelpers.cs @@ -0,0 +1,76 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Models +{ + using System.Net; + using System.Net.Sockets; + using System.Text.RegularExpressions; + + /// + /// The Dicom application entity + /// + public static class ApplicationEntityValidationHelpers + { + /// + /// The maximum valid port number (inclusive). + /// + public const int MaximumPortNumber = 65535; + + /// + /// The minimum valid port number (inclusive). + /// + public const int MinimumPortNumber = 3; + + /// + /// The valid hostname regex. + /// + private const string ValidHostnameRegex = @"^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$"; + + /// + /// Validates the application entity tile. The title must have a length of at least 1 and less than 17 characters long. + /// + /// If the application entity title is valid. + public static bool ValidateTitle(string title) + { + return !string.IsNullOrWhiteSpace(title) && title.Length >= 1 && title.Length <= 16; + } + + /// + /// Validates the application entity port is in the correct range. + /// + /// If the port is within the correct range. + public static bool ValidatePort(int port) + { + return (port >= MinimumPortNumber) && (port <= MaximumPortNumber); + } + + /// + /// Validates the IP address is correct and parses correctly. + /// + /// The IP address. + /// If the IP address is correct. + public static bool ValidateIPAddress(string address) + { + // Check if this is a valid host name + if (Regex.IsMatch(address, ValidHostnameRegex)) + { + return true; + } + + if (string.IsNullOrWhiteSpace(address) || !IPAddress.TryParse(address, out IPAddress parsedAddress)) + { + return false; + } + + switch (parsedAddress.AddressFamily) + { + case AddressFamily.InterNetwork: + return true; + case AddressFamily.InterNetworkV6: + // we have IPv6 + return true; + default: + // something unknown. + return false; + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomDataReceiverProgressEventArgs.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomDataReceiverProgressEventArgs.cs new file mode 100644 index 0000000..a8e9e65 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomDataReceiverProgressEventArgs.cs @@ -0,0 +1,92 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Models +{ + using System; + + using Dicom.Network; + + using Microsoft.InnerEye.Listener.DataProvider.Interfaces; + + /// + /// The Dicom data receiver update for an ongoing Dicom C-Store request. + /// + public class DicomDataReceiverProgressEventArgs : EventArgs + { + /// + /// Gets the saver used for this progress event. + /// + /// + /// The saver used for this progress event. + /// + private readonly IDicomSaver _dicomSaver; + + /// + /// Construct a progress update for an ongoing Dicom C-Store request. + /// + /// The image saver used for this progress event. + /// The progress code. + /// The date time the socket connection started. + /// The Dicom association. + /// The Dicom association identifier. + public DicomDataReceiverProgressEventArgs( + IDicomSaver dicomSaver, + DicomReceiveProgressCode progressCode, + DateTime socketConnectionDateTime, + DicomAssociation dicomAssociation, + Guid associationId) + { + _dicomSaver = dicomSaver ?? throw new ArgumentNullException(nameof(dicomSaver)); + + ProgressCode = progressCode; + SocketConnectionDateTime = socketConnectionDateTime; + DicomAssociation = dicomAssociation; + AssociationId = associationId; + } + + /// + /// Gets the current State of the association and store request. + /// + public DicomReceiveProgressCode ProgressCode { get; } + + /// + /// Gets the date time the socket connection started. + /// + public DateTime SocketConnectionDateTime { get; } + + /// + /// Gets the Dicom association + /// + public DicomAssociation DicomAssociation { get; } + + /// + /// Gets the association identifier. + /// + /// + /// The association identifier. + /// + public Guid AssociationId { get; } + + /// + /// Gets the folder path where the association data was stored. + /// + /// + /// The folder for this association where all data was saved. + /// + public string FolderPath => _dicomSaver.GetSaveFolderPath(AssociationId); + + /// + /// Gets the root folder path for the Dicom saver. + /// + /// + /// The root folder where association folders will be created. + /// + public string RootFolderPath => _dicomSaver.RootSaveFolder.FullName; + + /// + /// Gets if any data has been received for this association. + /// + /// + /// True if any data has been received. + /// + public bool AnyDataReceived => _dicomSaver.CheckIfAnyDataReceived(AssociationId); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomFileStoreParameters.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomFileStoreParameters.cs new file mode 100644 index 0000000..6e7571f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomFileStoreParameters.cs @@ -0,0 +1,56 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Models +{ + using System; + using System.Collections.Generic; + + using Dicom; + using Dicom.Network; + + using Interfaces; + + /// + /// Parameters to control the behaviour of and receive feedback from a Dicom File Store instance. + /// + public class DicomFileStoreParameters + { + /// + /// Initializes a new instance of the class. + /// + /// The update. + /// + /// The function for getting the accepted transfer syntaxes when an association is made. + /// + /// The Dicom saver. + /// + /// Update or AcceptAssociation or ImageSaver + /// + public DicomFileStoreParameters( + Action update, + Func> getAcceptedTransferSyntaxes, + IDicomSaver dicomSaver) + { + Update = update ?? throw new ArgumentNullException(nameof(update), "The update action cannot be null."); + GetAcceptedTransferSyntaxes = getAcceptedTransferSyntaxes ?? throw new ArgumentNullException(nameof(getAcceptedTransferSyntaxes), "The get accepted transfer syntaxes action cannot be null."); + DicomSaver = dicomSaver ?? throw new ArgumentNullException(nameof(dicomSaver), "The DICOM saver cannot be null."); + } + + /// + /// The action will be called as data is received from the network peer. + /// + public Action Update { get; } + + /// + /// Gets the get accepted transfer syntaxes. + /// + /// + /// The get accepted transfer syntaxes. + /// + public Func> GetAcceptedTransferSyntaxes { get; } + + /// + /// Gets a DICOM saver that will be used to serialize accepted DicomFiles received + /// from the network peer. + /// + public IDicomSaver DicomSaver { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomOperationResult.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomOperationResult.cs new file mode 100644 index 0000000..a81dcc1 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomOperationResult.cs @@ -0,0 +1,23 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Models +{ + /// + /// A Dicom operation result enumeration. + /// + public enum DicomOperationResult + { + /// + /// If the operation correctly executed. + /// + Success, + + /// + /// If the operation returned an error. + /// + Error, + + /// + /// If the operation did not get a response from the receiver. + /// + NoResponse, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomReceiveProgressCodes.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomReceiveProgressCodes.cs new file mode 100644 index 0000000..e8b8377 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomReceiveProgressCodes.cs @@ -0,0 +1,26 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Models +{ + /// + /// List of progress codes for receiving data over Dicom. + /// + public enum DicomReceiveProgressCode + { + FileReceived, + + ErrorSavingFile, + + ErrorCouldNotUnderstand, + + GenericStorageException, + + TransferAborted, + + AssociationEstablished, + + AssociationReleased, + + ConnectionClosed, + + Echo, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomStoreException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomStoreException.cs new file mode 100644 index 0000000..252fa30 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DataProvider/Models/DicomStoreException.cs @@ -0,0 +1,77 @@ +namespace Microsoft.InnerEye.Listener.DataProvider.Models +{ + using System; + using System.Runtime.Serialization; + using Dicom.Network; + + /// + /// Provide a meaningful message to the Store SCU when an error or warning occurs. . + /// + [Serializable] + public class DicomStoreException : Exception + { + /// + /// Default constructor. + /// + public DicomStoreException() + : base() + { + } + + /// + /// Constructor. + /// + /// The exception message. + public DicomStoreException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The serialization information. + /// The streaming context. + protected DicomStoreException(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base(serializationInfo, streamingContext) + { + } + + /// + /// Constructor. + /// + /// The exception message. + /// The exception. + public DicomStoreException(string message, Exception exception) + : base(message, exception) + { + } + + /// + /// Constructor. + /// + /// The Dicom status. + /// The message. + public DicomStoreException(DicomStatus status, string message) + : base(message) + { + Status = status; + } + + /// + /// Use the status field to inform the SCU in a meaningful way about the error or warning + /// that occurred. + /// + public DicomStatus Status { get; private set; } + + /// + /// Added an implementation of get object data for serializable class. + /// + /// The serialization information. + /// The streaming context. + public override void GetObjectData(SerializationInfo info, StreamingContext context) + { + base.GetObjectData(info, context); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/GroupTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/GroupTests.cs new file mode 100644 index 0000000..6826920 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/GroupTests.cs @@ -0,0 +1,38 @@ +namespace Microsoft.InnerEye.DicomConstraints.Tests +{ + using Dicom; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class GroupTests + { + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsLogicalCombo() + { + var c0 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedIntConstraint(Order.Equal, 42, DicomTag.SeriesNumber)); + var c1 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedDoubleConstraint(Order.GreaterThan, 3.141, DicomTag.PixelSpacing, 0)); + var c2 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedDoubleConstraint(Order.LessThan, 6.0, DicomTag.PixelSpacing, 1)); + var c3t = new StringContainsConstraint(DicomTag.PatientPosition, "HFS"); + var c3 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, c3t); + + var groupConstraintAnd = new GroupConstraint(new DicomConstraint[] { c0, c1, c2, c3 }, LogicalOperator.And); + var groupConstraintOr = new GroupConstraint(new DicomConstraint[] { c0, c1, c2, c3 }, LogicalOperator.Or); + + var ds = new DicomDataset + { + { DicomTag.SeriesNumber, 42 }, + { DicomTag.PixelSpacing, new decimal[] { 3.142M, 3.142M } }, + { DicomTag.PatientPosition, "HFS" }, + }; + + Assert.IsTrue(groupConstraintAnd.Check(ds).Result); + Assert.IsTrue(groupConstraintOr.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.PatientPosition, "FAIL"); + Assert.IsFalse(groupConstraintAnd.Check(ds).Result); + Assert.IsTrue(groupConstraintOr.Check(ds).Result); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/Microsoft.InnerEye.DicomConstraints.Tests.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/Microsoft.InnerEye.DicomConstraints.Tests.csproj new file mode 100644 index 0000000..5ab4d87 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/Microsoft.InnerEye.DicomConstraints.Tests.csproj @@ -0,0 +1,33 @@ + + + net462 + x64 + Microsoft.InnerEye.DicomConstraints.Tests + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Tests for Microsoft.InnerEye.DicomConstraints for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + true + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/OrderConstraintTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/OrderConstraintTests.cs new file mode 100644 index 0000000..7ff7160 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/OrderConstraintTests.cs @@ -0,0 +1,161 @@ +namespace Microsoft.InnerEye.DicomConstraints.Tests +{ + using System; + using System.Text.RegularExpressions; + + using Dicom; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OrderConstraintTests + { + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsTestOrder() + { + var o0 = new OrderedIntConstraint(Order.Never, 42, DicomTag.SeriesNumber); + var o1 = new OrderedIntConstraint(Order.LessThan, 42, DicomTag.SeriesNumber); + var o2 = new OrderedIntConstraint(Order.LessThanOrEqual, 42, DicomTag.SeriesNumber); + var o3 = new OrderedIntConstraint(Order.Equal, 42, DicomTag.SeriesNumber); + var o4 = new OrderedIntConstraint(Order.GreaterThan, 42, DicomTag.SeriesNumber); + var o5 = new OrderedIntConstraint(Order.GreaterThanOrEqual, 42, DicomTag.SeriesNumber); + var o6 = new OrderedIntConstraint(Order.NotEqual, 42, DicomTag.SeriesNumber); + var o7 = new OrderedIntConstraint(Order.Always, 42, DicomTag.SeriesNumber); + + DicomDataset ds = new DicomDataset + { + { DicomTag.SeriesNumber, 41 }, + }; + + Assert.IsFalse(o0.Check(ds).Result); + Assert.IsTrue(o1.Check(ds).Result); + Assert.IsTrue(o2.Check(ds).Result); + Assert.IsFalse(o3.Check(ds).Result); + Assert.IsFalse(o4.Check(ds).Result); + Assert.IsFalse(o5.Check(ds).Result); + Assert.IsTrue(o6.Check(ds).Result); + Assert.IsTrue(o7.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.SeriesNumber, 42); + Assert.IsFalse(o0.Check(ds).Result); + Assert.IsFalse(o1.Check(ds).Result); + Assert.IsTrue(o2.Check(ds).Result); + Assert.IsTrue(o3.Check(ds).Result); + Assert.IsFalse(o4.Check(ds).Result); + Assert.IsTrue(o5.Check(ds).Result); + Assert.IsFalse(o6.Check(ds).Result); + Assert.IsTrue(o7.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.SeriesNumber, 43); + Assert.IsFalse(o0.Check(ds).Result); + Assert.IsFalse(o1.Check(ds).Result); + Assert.IsFalse(o2.Check(ds).Result); + Assert.IsFalse(o3.Check(ds).Result); + Assert.IsTrue(o4.Check(ds).Result); + Assert.IsTrue(o5.Check(ds).Result); + Assert.IsTrue(o6.Check(ds).Result); + Assert.IsTrue(o7.Check(ds).Result); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsTime() + { + var o0 = new TimeOrderConstraint(Order.Never, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o1 = new TimeOrderConstraint(Order.LessThan, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o2 = new TimeOrderConstraint(Order.LessThanOrEqual, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o3 = new TimeOrderConstraint(Order.Equal, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o4 = new TimeOrderConstraint(Order.GreaterThan, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o5 = new TimeOrderConstraint(Order.GreaterThanOrEqual, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o6 = new TimeOrderConstraint(Order.NotEqual, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + var o7 = new TimeOrderConstraint(Order.Always, new TimeSpan(11, 11, 11), DicomTag.SeriesTime); + + DicomDataset ds = new DicomDataset + { + { DicomTag.SeriesTime, new DateTime(2017, 2, 14, 10, 11, 11) }, + }; + + Assert.IsFalse(o0.Check(ds).Result); + Assert.IsTrue(o1.Check(ds).Result); + Assert.IsTrue(o2.Check(ds).Result); + Assert.IsFalse(o3.Check(ds).Result); + Assert.IsFalse(o4.Check(ds).Result); + Assert.IsFalse(o5.Check(ds).Result); + Assert.IsTrue(o6.Check(ds).Result); + Assert.IsTrue(o7.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.SeriesTime, new DateTime(2017, 2, 14, 11, 11, 11)); + Assert.IsFalse(o0.Check(ds).Result); + Assert.IsFalse(o1.Check(ds).Result); + Assert.IsTrue(o2.Check(ds).Result); + Assert.IsTrue(o3.Check(ds).Result); + Assert.IsFalse(o4.Check(ds).Result); + Assert.IsTrue(o5.Check(ds).Result); + Assert.IsFalse(o6.Check(ds).Result); + Assert.IsTrue(o7.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.SeriesTime, new DateTime(2017, 2, 14, 13, 11, 11)); + Assert.IsFalse(o0.Check(ds).Result); + Assert.IsFalse(o1.Check(ds).Result); + Assert.IsFalse(o2.Check(ds).Result); + Assert.IsFalse(o3.Check(ds).Result); + Assert.IsTrue(o4.Check(ds).Result); + Assert.IsTrue(o5.Check(ds).Result); + Assert.IsTrue(o6.Check(ds).Result); + Assert.IsTrue(o7.Check(ds).Result); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintRegex() + { + var c0 = new RegexConstraint(DicomTag.SeriesDescription, @"(hog)", RegexOptions.ECMAScript); + var c0R = new RequiredTagConstraint(TagRequirement.Optional, c0); + + DicomDataset ds = new DicomDataset(); + ds.AddOrUpdate(DicomTag.SeriesDescription, @"hog protocol v1"); + + Assert.IsTrue(c0R.Check(ds).Result); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintUID() + { + DicomDataset ds = new DicomDataset(); + ds.AddOrUpdate(DicomTag.SOPClassUID, DicomUID.CTImageStorage); + + var s0 = new UIDStringOrderConstraint(Order.Equal, DicomUID.CTImageStorage.UID, DicomTag.SOPClassUID); + var c0 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, s0); + Assert.IsTrue(c0.Check(ds).Result); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintCodeString() + { + DicomDataset ds = new DicomDataset(); + ds.AddOrUpdate(DicomTag.BodyPartExamined, "PELVIS"); + + var d0 = new OrderedStringConstraint(Order.NotEqual, "SKULL", DicomTag.BodyPartExamined); + var c0 = new RequiredTagConstraint(TagRequirement.Optional, d0); + Assert.IsTrue(c0.Check(ds).Result); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintCodeStringCases() + { + DicomDataset ds = new DicomDataset(); + ds.AddOrUpdate(DicomTag.BodyPartExamined, "PELVIS"); + + var orderedCaseSensitive = new OrderedString("pElViS", StringComparisonType.CultureInvariantIgnoreCase); + var orderedCaseInsensitive = new OrderedString("pElViS", StringComparisonType.CulureInvariantCaseSensitive); + var d0 = new OrderedStringConstraint(Order.Equal, orderedCaseSensitive, DicomTag.BodyPartExamined); + Assert.IsTrue(d0.Check(ds).Result); + var d1 = new OrderedStringConstraint(Order.NotEqual, orderedCaseInsensitive, DicomTag.BodyPartExamined); + Assert.IsTrue(d1.Check(ds).Result); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/SerializationTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/SerializationTests.cs new file mode 100644 index 0000000..1b714ba --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/SerializationTests.cs @@ -0,0 +1,153 @@ +namespace Microsoft.InnerEye.DicomConstraints.Tests +{ + using System; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Text.RegularExpressions; + + using Dicom; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Newtonsoft.Json; + + [TestClass] + public class SerializationTests + { + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsTestSerialize() + { + var c0 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedIntConstraint(Order.Equal, 42, DicomTag.SeriesNumber)); + var c1 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedDoubleConstraint(Order.GreaterThan, 3.141, DicomTag.PixelSpacing, 0)); + var c2 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedDoubleConstraint(Order.LessThan, 6.0, DicomTag.PixelSpacing, 1)); + var c3t = new StringContainsConstraint(DicomTag.PatientPosition, "HFS"); + var c3 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, c3t); + + var groupConstraintAnd = new GroupConstraint(new DicomConstraint[] { c0, c1, c2, c3 }, LogicalOperator.And); + var groupConstraintOr = new GroupConstraint(new DicomConstraint[] { c0, c1, c2, c3 }, LogicalOperator.Or); + + var ds1 = new DicomDataset + { + { DicomTag.SeriesNumber, 42 }, + { DicomTag.PixelSpacing, new decimal[] { 3.142M, 3.142M } }, + { DicomTag.PatientPosition, "HFS" }, + }; + + Assert.IsTrue(groupConstraintAnd.Check(ds1).Result); + Assert.IsTrue(groupConstraintOr.Check(ds1).Result); + + var ds2 = new DicomDataset + { + { DicomTag.SeriesNumber, 42 }, + { DicomTag.PixelSpacing, new decimal[] { 0.5M, 3.142M } }, + { DicomTag.PatientPosition, "HFS" }, + }; + + Assert.IsFalse(groupConstraintAnd.Check(ds2).Result); + Assert.IsTrue(groupConstraintOr.Check(ds2).Result); + + var ss1 = JsonConvert.SerializeObject(groupConstraintAnd); + var ss2 = JsonConvert.SerializeObject(groupConstraintOr); + + var groupConstraintAndDS = JsonConvert.DeserializeObject(ss1); + var groupConstraintOrDS = JsonConvert.DeserializeObject(ss2); + + Assert.IsTrue(groupConstraintAndDS.Check(ds1).Result); + Assert.IsTrue(groupConstraintOrDS.Check(ds1).Result); + + Assert.IsFalse(groupConstraintAndDS.Check(ds2).Result); + Assert.IsTrue(groupConstraintOrDS.Check(ds2).Result); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsNestedSerialize() + { + var c0 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedIntConstraint(Order.Equal, 42, DicomTag.SeriesNumber)); + var c1 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedDoubleConstraint(Order.GreaterThan, 3.141f, DicomTag.PixelSpacing, 0)); + var c2 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, new OrderedDoubleConstraint(Order.LessThan, 6.0f, DicomTag.PixelSpacing, 1)); + var c3t = new StringContainsConstraint(DicomTag.PatientPosition, "HFS"); + var c3 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, c3t); + + var groupConstraintAnd1 = new GroupConstraint(new DicomConstraint[] { c0, c1 }, LogicalOperator.And); + var groupConstraintAnd2 = new GroupConstraint(new DicomConstraint[] { c2, c3 }, LogicalOperator.And); + + var groupConstraintOr = new GroupConstraint(new DicomConstraint[] { groupConstraintAnd1, groupConstraintAnd2 }, LogicalOperator.Or); + + var ds1 = new DicomDataset + { + { DicomTag.SeriesNumber, 42 }, + { DicomTag.PixelSpacing, new decimal[] { 3.142M, 3.142M } }, + { DicomTag.PatientPosition, "HFS" }, + }; + + Assert.IsTrue(groupConstraintOr.Check(ds1).Result); + + // Serialize + var ss2 = JsonConvert.SerializeObject(groupConstraintOr); + + var groupConstraintOrDS = JsonConvert.DeserializeObject(ss2); + + Assert.IsTrue(groupConstraintOrDS.Check(ds1).Result); + } + + /// + /// Used to generate the current set of constraint types + /// + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsGenTypes() + { + var orderInt = new OrderedIntConstraint(Order.Equal, int.MinValue, DicomTag.SeriesNumber); + var orderDouble = new OrderedDoubleConstraint(Order.Equal, double.MaxValue, DicomTag.SeriesNumber); + var orderDateTime = new OrderedDateTimeConstraint(Order.Equal, DateTime.UtcNow, DicomTag.SeriesNumber); + var orderString = new OrderedStringConstraint(Order.Equal, new OrderedString("hOOf", StringComparisonType.CulureInvariantCaseSensitive), DicomTag.SeriesNumber); + + var stringOrderUID = new UIDStringOrderConstraint(Order.Equal, DicomUID.CTImageStorage.UID, DicomTag.SOPClassUID); + var timeOrder = new TimeOrderConstraint(Order.GreaterThanOrEqual, new TimeSpan(0, 0, 0), DicomTag.SeriesTime); + var stringContains = new StringContainsConstraint(DicomTag.PatientPosition, "HFS"); + var stringRegex = new RegexConstraint(DicomTag.SeriesDescription, "(hog)", RegexOptions.ECMAScript); + var compareStrings = new[] { "HOOF", "LEAF", "FLOWER", "HOG" }.Select(t => new OrderedString(t)); + var stringCompountGroup = new GroupConstraint(compareStrings.Select(c => new OrderedStringConstraint(Order.Equal, c, DicomTag.BodyPartExamined)).ToArray(), LogicalOperator.Or); + var stringCompound = new GroupTagConstraint(stringCompountGroup, DicomTag.BodyPartExamined); + + DicomConstraint[] dc = new[] + { + new RequiredTagConstraint(TagRequirement.Optional, orderInt), + new RequiredTagConstraint(TagRequirement.Optional, orderDouble), + new RequiredTagConstraint(TagRequirement.Optional, orderDateTime), + new RequiredTagConstraint(TagRequirement.Optional, orderString), + new RequiredTagConstraint(TagRequirement.Optional, stringOrderUID), + new RequiredTagConstraint(TagRequirement.PresentCanBeEmpty, timeOrder), + new RequiredTagConstraint(TagRequirement.PresentCanBeEmpty, stringContains), + new RequiredTagConstraint(TagRequirement.Optional, stringRegex), + new RequiredTagConstraint(TagRequirement.PresentNotEmpty, stringOrderUID), + new RequiredTagConstraint(TagRequirement.Optional, orderString), + new RequiredTagConstraint(TagRequirement.Optional, stringCompound), + }; + + var dc2 = new GroupConstraint(dc, LogicalOperator.Or) as DicomConstraint; + JsonConvert.SerializeObject(dc2); + } + + [TestCategory("DicomConstraints")] + [TestMethod] + public void TestCurrentTypes() + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = "Microsoft.InnerEye.DicomConstraints.Tests.currenttypes.json"; + + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + { + StreamReader reader = new StreamReader(stream); + string result = reader.ReadToEnd(); + + var constraintGroup = JsonConvert.DeserializeObject(result); + DicomDataset ds = new DicomDataset(); + constraintGroup.Check(ds); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/TagRequirements.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/TagRequirements.cs new file mode 100644 index 0000000..a7aa504 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/TagRequirements.cs @@ -0,0 +1,41 @@ +namespace Microsoft.InnerEye.DicomConstraints.Tests +{ + using Dicom; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class TagRequirements + { + [TestCategory("DicomConstraints")] + [TestMethod] + public void ConstraintsTestOptionality() + { + DicomDataset ds = new DicomDataset(); + + var c0 = new OrderedIntConstraint(Order.Equal, 4, DicomTag.SeriesNumber); + + var c3 = new RequiredTagConstraint(TagRequirement.Optional, c0); + var c2 = new RequiredTagConstraint(TagRequirement.PresentCanBeEmpty, c0); + var c1 = new RequiredTagConstraint(TagRequirement.PresentNotEmpty, c0); + + Assert.IsTrue(c3.Check(ds).Result); + Assert.IsFalse(c2.Check(ds).Result); + Assert.IsFalse(c1.Check(ds).Result); + + ds.Add(DicomTag.SeriesNumber, string.Empty); + Assert.IsTrue(c3.Check(ds).Result); + Assert.IsTrue(c2.Check(ds).Result); + Assert.IsFalse(c1.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.SeriesNumber, "4"); + Assert.IsTrue(c3.Check(ds).Result); + Assert.IsTrue(c2.Check(ds).Result); + Assert.IsTrue(c1.Check(ds).Result); + + ds.AddOrUpdate(DicomTag.SeriesNumber, "3"); + Assert.IsFalse(c3.Check(ds).Result); + Assert.IsFalse(c2.Check(ds).Result); + Assert.IsFalse(c1.Check(ds).Result); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/currenttypes.json b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/currenttypes.json new file mode 100644 index 0000000..050d4a6 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints.Tests/currenttypes.json @@ -0,0 +1,250 @@ +{ + "discriminator": "GroupConstraint", + "Constraints": [ + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "OrderedIntConstraint", + "Function": { + "Order": "Equal", + "Value": -2147483648, + "Ordinal": 0 + }, + "Index": { + "Group": 32, + "Element": 17 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "OrderedDoubleConstraint", + "Function": { + "Order": "Equal", + "Value": 1.7976931348623157E+308, + "Ordinal": 0 + }, + "Index": { + "Group": 32, + "Element": 17 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "OrderedDateTimeConstraint", + "Function": { + "Order": "Equal", + "Value": "2017-11-17T15:39:02.0088681Z", + "Ordinal": 0 + }, + "Index": { + "Group": 32, + "Element": 17 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "OrderedStringConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "hOOf", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 32, + "Element": 17 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "UIDStringOrderConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "PresentCanBeEmpty", + "Constraint": { + "discriminator": "TimeOrderConstraint", + "Function": { + "Order": "GreaterThanOrEqual", + "Value": "00:00:00", + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 49 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "PresentCanBeEmpty", + "Constraint": { + "discriminator": "StringContainsConstraint", + "Match": "HFS", + "Ordinal": 0, + "Index": { + "Group": 24, + "Element": 20736 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "RegexConstraint", + "Expression": "(hog)", + "Options": "ECMAScript", + "Ordinal": 0, + "Index": { + "Group": 8, + "Element": 4158 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "PresentNotEmpty", + "Constraint": { + "discriminator": "UIDStringOrderConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "1.2.840.10008.5.1.4.1.1.2", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 8, + "Element": 22 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "OrderedStringConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "hOOf", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 32, + "Element": 17 + } + } + }, + { + "discriminator": "RequiredTagConstraint", + "RequirementLevel": "Optional", + "Constraint": { + "discriminator": "GroupTagConstraint", + "Group": { + "discriminator": "GroupConstraint", + "Constraints": [ + { + "discriminator": "OrderedStringConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "HOOF", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 24, + "Element": 21 + } + }, + { + "discriminator": "OrderedStringConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "LEAF", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 24, + "Element": 21 + } + }, + { + "discriminator": "OrderedStringConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "FLOWER", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 24, + "Element": 21 + } + }, + { + "discriminator": "OrderedStringConstraint", + "Function": { + "Order": "Equal", + "Value": { + "Value": "HOG", + "ComparisonType": 0 + }, + "Ordinal": 0 + }, + "Index": { + "Group": 24, + "Element": 21 + } + } + ], + "Op": "Or" + }, + "Index": { + "Group": 24, + "Element": 21 + } + } + } + ], + "Op": "Or" +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraint.cs new file mode 100644 index 0000000..574b0c7 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraint.cs @@ -0,0 +1,32 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using Dicom; + using Newtonsoft.Json; + using NJsonSchema.Converters; + using System.Runtime.Serialization; + + /// + /// Impose a constraint on a DICOM dataset + /// + [JsonConverter(typeof(JsonInheritanceConverter), "discriminator")] + [KnownType(typeof(OrderedIntConstraint))] + [KnownType(typeof(OrderedDoubleConstraint))] + [KnownType(typeof(OrderedDateTimeConstraint))] + [KnownType(typeof(OrderedStringConstraint))] + [KnownType(typeof(TimeOrderConstraint))] + [KnownType(typeof(RegexConstraint))] + [KnownType(typeof(UIDStringOrderConstraint))] + [KnownType(typeof(RequiredTagConstraint))] + [KnownType(typeof(GroupConstraint))] + [KnownType(typeof(GroupTagConstraint))] + [KnownType(typeof(StringContainsConstraint))] + public abstract class DicomConstraint + { + /// + /// Check that the dataset conforms to this constraint. + /// + /// + /// + public abstract DicomConstraintResult Check(DicomDataset dataSet); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraintResult.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraintResult.cs new file mode 100644 index 0000000..3431ec0 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomConstraintResult.cs @@ -0,0 +1,58 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + + /// + /// Dicom constraint result. + /// + public class DicomConstraintResult + { + /// + /// Initializes a new instance of the class. + /// + /// if set to true [result]. + /// The constraint. + public DicomConstraintResult(bool result, DicomConstraint constraint) + { + Result = result; + Constraint = constraint ?? throw new ArgumentNullException(nameof(constraint)); + } + + /// + /// Initializes a new instance of the class. + /// + /// if set to true [result]. + /// The constraint. + /// The child results. + public DicomConstraintResult(bool result, DicomConstraint constraint, params DicomConstraintResult[] childResults) + : this(result, constraint) + { + ChildResults = childResults ?? throw new ArgumentNullException(nameof(childResults)); + } + + /// + /// Gets a the check result of the Dicom constraint. + /// + /// + /// true if result; otherwise, false. + /// + public bool Result { get; } + + /// + /// Gets the constraint. + /// + /// + /// The constraint. + /// + public DicomConstraint Constraint { get; } + + /// + /// Gets the child results. + /// + /// + /// The child results. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "TBD")] + public DicomConstraintResult[] ChildResults { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomOrderedTag.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomOrderedTag.cs new file mode 100644 index 0000000..1b83287 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomOrderedTag.cs @@ -0,0 +1,87 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Encodes an ordering function on a DicomTag + /// + /// + public class DicomOrderedTag : IEquatable> + { + /// + /// Constructor + /// + /// + /// + /// + [JsonConstructor] + public DicomOrderedTag(Order order, T value, int ordinal = 0) + { + Order = order; + Value = value; + Ordinal = ordinal; + } + + /// + /// The ordering function to apply between this Value and the tag value + /// + [JsonConverter(typeof(StringEnumConverter))] + [Required] + public Order Order { get; } + + /// + /// The value to use in the constraint function + /// + [Required] + public T Value { get; } + + /// + /// The Ordinal we wish to extract from the tag or -1 to extract all + /// + [Required] + [Range(-1, int.MaxValue)] + public int Ordinal { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as DicomOrderedTag); + } + + /// + public bool Equals(DicomOrderedTag other) + { + return other != null && + Order == other.Order && + EqualityComparer.Default.Equals(Value, other.Value) && + Ordinal == other.Ordinal; + } + + /// + public override int GetHashCode() + { + var hashCode = 1130906755; + hashCode = hashCode * -1521134295 + Order.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); + hashCode = hashCode * -1521134295 + Ordinal.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(DicomOrderedTag left, DicomOrderedTag right) + { + return EqualityComparer>.Default.Equals(left, right); + } + + /// + public static bool operator !=(DicomOrderedTag left, DicomOrderedTag right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagConstraint.cs new file mode 100644 index 0000000..7641203 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagConstraint.cs @@ -0,0 +1,34 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.ComponentModel.DataAnnotations; + using Dicom; + + /// + /// The Dicom tag constraint. + /// + /// + public class DicomTagConstraint : DicomConstraint + { + /// + /// Construct a new instance + /// + /// + protected DicomTagConstraint(DicomTagIndex index) + { + Index = index; + } + + /// + /// The tag you wish to constrain + /// + [Required] + public DicomTagIndex Index { get; } + + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagIndex.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagIndex.cs new file mode 100644 index 0000000..7371643 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/DicomTagIndex.cs @@ -0,0 +1,93 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + + using Dicom; + + using Newtonsoft.Json; + + /// + /// Json serializable DicomTag encoding + /// + public class DicomTagIndex : IEquatable + { + /// + /// Constructor for DICOM tag and index + /// + [JsonConstructor] + public DicomTagIndex(ushort group, ushort element) + { + Group = group; + Element = element; + } + + /// + /// Constructor for DICOM tag and index + /// + /// + public DicomTagIndex(DicomTag tag) + : this(tag?.Group ?? 0, tag?.Element ?? 0) + { + } + + /// + /// The group index of this tag + /// + [Required] + [Range(0, 65535)] + public ushort Group { get; } + + /// + /// The element index of this tag + /// + [Required] + [Range(0, 65535)] + public ushort Element { get; } + + /// + /// Gets the dicom tag. + /// + /// + /// The dicom tag. + /// + [JsonIgnore] + public DicomTag DicomTag => new DicomTag(Group, Element); + + /// + public override bool Equals(object obj) + { + return Equals(obj as DicomTagIndex); + } + + /// + public bool Equals(DicomTagIndex other) + { + return other != null && + Group == other.Group && + Element == other.Element; + } + + /// + public override int GetHashCode() + { + var hashCode = -1357862800; + hashCode = hashCode * -1521134295 + Group.GetHashCode(); + hashCode = hashCode * -1521134295 + Element.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(DicomTagIndex left, DicomTagIndex right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(DicomTagIndex left, DicomTagIndex right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/GroupConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/GroupConstraint.cs new file mode 100644 index 0000000..d94e259 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/GroupConstraint.cs @@ -0,0 +1,197 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Linq; + using Dicom; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Describes how to group sequential constraints + /// + public enum LogicalOperator + { + /// + /// And operator + /// + And, + + /// + /// Or operator + /// + Or, + } + + /// + /// A group of constraints that must either all pass or at least 1 pass for this constraint to pass. + /// + public class GroupConstraint : DicomConstraint, IEquatable + { + /// + /// Construct a new constrain given a list of constraints and a operator to combine them + /// + /// + /// + [JsonConstructor] + public GroupConstraint(DicomConstraint[] constraints, LogicalOperator op) + { + Constraints = constraints ?? throw new ArgumentNullException(nameof(constraints)); + Op = op; + } + + internal GroupConstraint() + { + Constraints = new DicomConstraint[0]; + Op = LogicalOperator.And; + } + + /// + /// The ordered list of constraints to apply to the dataset + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "TBD")] + [Required] + public DicomConstraint[] Constraints { get; } + + /// + /// The operator for combining Constraints + /// + [JsonConverter(typeof(StringEnumConverter))] + [Required] + public LogicalOperator Op { get; } + + /// + /// Check the dataset passes this group constraint + /// + /// + /// If dataset is null + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + if (dataSet == null) + { + throw new ArgumentNullException(nameof(dataSet)); + } + + var childResults = Constraints.Select(c => c.Check(dataSet)).ToArray(); + var result = Op == LogicalOperator.And ? childResults.All(c => c.Result) : childResults.Any(c => c.Result); + + return new DicomConstraintResult(result, this, childResults); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as GroupConstraint); + } + + /// + public bool Equals(GroupConstraint other) + { + return other != null && + Constraints.SequenceEqual(other.Constraints) && + Op == other.Op; + } + + /// + public override int GetHashCode() + { + var hashCode = 985604525; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Constraints); + hashCode = hashCode * -1521134295 + Op.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(GroupConstraint left, GroupConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(GroupConstraint left, GroupConstraint right) + { + return !(left == right); + } + } + + /// + /// Impose a group constraint on an individual tag. + /// + public class GroupTagConstraint : DicomTagConstraint, IEquatable + { + /// + /// Impose a group constraint on an indvidual tag. + /// + /// + /// + [JsonConstructor] + public GroupTagConstraint(GroupConstraint group, DicomTagIndex index) + : base(index) + { + Group = group ?? throw new ArgumentNullException(nameof(group)); + } + + /// + /// Impose a group constraint on an indvidual tag. + /// + /// + /// + public GroupTagConstraint(GroupConstraint group, DicomTag tag) + : this(group, new DicomTagIndex(tag)) + { + Group = group ?? throw new ArgumentNullException(nameof(group)); + } + + /// + /// The Group constraint on Index + /// + public GroupConstraint Group { get; } + + /// + /// Ask the Group to check the constraint + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return Group.Check(dataSet); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as GroupTagConstraint); + } + + /// + public bool Equals(GroupTagConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer.Default.Equals(Group, other.Group); + } + + /// + public override int GetHashCode() + { + var hashCode = -2043322065; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Group); + return hashCode; + } + + /// + public static bool operator ==(GroupTagConstraint left, GroupTagConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(GroupTagConstraint left, GroupTagConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/Order.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/Order.cs new file mode 100644 index 0000000..a2dbb74 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/Order.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + /// + /// Encodes an operator on a pair of comparable items. + /// + public enum Order : int + { + /// + /// For all A & B : A Never B = false + /// + Never = 0, + + /// + /// For all A & B : A LessThan B => A < B + /// + LessThan = 1, + + /// + /// For all A & B : A Equal B => A = B + /// + Equal = 2, + + /// + /// For all A & B : A LessThanOrEqual B => (A LessThan B || A Equals B) + /// + LessThanOrEqual = 3, + + /// + /// For all A & B : A GreaterThan B => A > B + /// + GreaterThan = 4, + + /// + /// For all A & B : A NotEqual B => A LessThan B || A GreaterThan B + /// + NotEqual = 5, + + /// + /// For all A & B : A GreaterThanOrEqual B => A Equal B | A GreaterThan B + /// + GreaterThanOrEqual = 6, + + /// + /// For all A & B : A Always B => true (A < LessThan B | A Equal B | A GreaterThan B) + /// + Always = 7, + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDateTimeConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDateTimeConstraint.cs new file mode 100644 index 0000000..798ceaa --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDateTimeConstraint.cs @@ -0,0 +1,90 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// Serialization type for OrderConstraint char + /// + public class OrderedDateTimeConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + /// + /// Ordering function parameters + [JsonConstructor] + public OrderedDateTimeConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public OrderedDateTimeConstraint(Order order, DateTime value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Checks that the tag in the given dataset satiisfies the ordering function + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint>. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as OrderedDateTimeConstraint); + } + + /// + public bool Equals(OrderedDateTimeConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer>.Default.Equals(Function, other.Function); + } + + /// + public override int GetHashCode() + { + var hashCode = 426273644; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Function); + return hashCode; + } + + /// + public static bool operator ==(OrderedDateTimeConstraint left, OrderedDateTimeConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(OrderedDateTimeConstraint left, OrderedDateTimeConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDoubleConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDoubleConstraint.cs new file mode 100644 index 0000000..852bdfe --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedDoubleConstraint.cs @@ -0,0 +1,90 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// Serialization type for OrderConstraint char + /// + public class OrderedDoubleConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + /// + /// Ordering function parameters + [JsonConstructor] + public OrderedDoubleConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public OrderedDoubleConstraint(Order order, double value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Checks that the tag in the given dataset satiisfies the ordering function + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint>. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as OrderedDoubleConstraint); + } + + /// + public bool Equals(OrderedDoubleConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer>.Default.Equals(Function, other.Function); + } + + /// + public override int GetHashCode() + { + var hashCode = 426273644; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Function); + return hashCode; + } + + /// + public static bool operator ==(OrderedDoubleConstraint left, OrderedDoubleConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(OrderedDoubleConstraint left, OrderedDoubleConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedIntConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedIntConstraint.cs new file mode 100644 index 0000000..ab8d764 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedIntConstraint.cs @@ -0,0 +1,90 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// Serialization type for OrderConstraint char + /// + public class OrderedIntConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + /// + /// Ordering function parameters + [JsonConstructor] + public OrderedIntConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public OrderedIntConstraint(Order order, int value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Checks that the tag in the given dataset satiisfies the ordering function + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint>. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as OrderedIntConstraint); + } + + /// + public bool Equals(OrderedIntConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer>.Default.Equals(Function, other.Function); + } + + /// + public override int GetHashCode() + { + var hashCode = 426273644; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Function); + return hashCode; + } + + /// + public static bool operator ==(OrderedIntConstraint left, OrderedIntConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(OrderedIntConstraint left, OrderedIntConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedString.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedString.cs new file mode 100644 index 0000000..b47418d --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedString.cs @@ -0,0 +1,146 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Newtonsoft.Json; + + /// + /// The string comparison function to use to impose an + /// + public enum StringComparisonType + { + /// + /// CulureInvariantCaseSensitive + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Culure", Justification = "TBD")] + CulureInvariantCaseSensitive = 0, + + /// + /// CultureInvariantIgnoreCase + /// + CultureInvariantIgnoreCase, + } + + /// + /// A simple string wrapper that can be used for string comparisons with the BaseOrderConstraint class. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1036:OverrideMethodsOnComparableTypes", Justification = "TBD")] + public class OrderedString : IComparable, IInitializable, IEquatable + { + /// + /// Construct a new ordering of the given Value using the StringComparisonType specified + /// + /// + /// + [JsonConstructor] + public OrderedString(string value, StringComparisonType comparisonType = StringComparisonType.CulureInvariantCaseSensitive) + { + Value = value ?? throw new ArgumentNullException(nameof(value)); + ComparisonType = comparisonType; + } + + /// + /// Constructor + /// + public OrderedString() + { + Value = string.Empty; + ComparisonType = StringComparisonType.CulureInvariantCaseSensitive; + } + + /// + /// The value you wish to compare to + /// + [Required] + public string Value { get; private set; } + + /// + /// How you would like to compare with Value + /// + [Required] + public StringComparisonType ComparisonType { get; } + + /// + /// implicitely convert value to an OrderString with CulureInvariantCaseSensitive comparison + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2225:OperatorOverloadsHaveNamedAlternates", Justification = "TBD")] + public static implicit operator OrderedString(string value) => new OrderedString(value); + + /// + /// IComparable implementation + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "TBD")] + public int CompareTo(object obj) + { + if (obj is OrderedString) + { + var objOrdered = obj as OrderedString; + switch (ComparisonType) + { + default: + case StringComparisonType.CulureInvariantCaseSensitive: + { + return StringComparer.InvariantCulture.Compare(Value, objOrdered.Value); + } + + case StringComparisonType.CultureInvariantIgnoreCase: + { + return StringComparer.InvariantCultureIgnoreCase.Compare(Value, objOrdered.Value); + } + } + } + else + { + throw new ArgumentException("Parameter not an OrderedString", nameof(obj)); + } + } + + /// + /// Implementation of IInitializable + /// + /// + public void Init(string source) + { + Value = source; + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as OrderedString); + } + + /// + public bool Equals(OrderedString other) + { + return other != null && + Value == other.Value && + ComparisonType == other.ComparisonType; + } + + /// + public override int GetHashCode() + { + var hashCode = 1031581766; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Value); + hashCode = hashCode * -1521134295 + ComparisonType.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(OrderedString left, OrderedString right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(OrderedString left, OrderedString right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedStringConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedStringConstraint.cs new file mode 100644 index 0000000..ed22d1c --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedStringConstraint.cs @@ -0,0 +1,90 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// Serialization type for OrderConstraint char + /// + public class OrderedStringConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + /// + /// Ordering function parameters + [JsonConstructor] + public OrderedStringConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public OrderedStringConstraint(Order order, OrderedString value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Checks that the tag in the given dataset satiisfies the ordering function + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint>. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as OrderedStringConstraint); + } + + /// + public bool Equals(OrderedStringConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer>.Default.Equals(Function, other.Function); + } + + /// + public override int GetHashCode() + { + var hashCode = 426273644; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Function); + return hashCode; + } + + /// + public static bool operator ==(OrderedStringConstraint left, OrderedStringConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(OrderedStringConstraint left, OrderedStringConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedUIDStringSelector.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedUIDStringSelector.cs new file mode 100644 index 0000000..211054b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/OrderedUIDStringSelector.cs @@ -0,0 +1,92 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + // File name should match first type name +#pragma warning disable SA1649 + + /// + /// Impose a constraint on a DicomUID + /// + public class UIDStringOrderConstraint : DicomTagConstraint, IEquatable +#pragma warning restore SA1649 + { + /// + /// Constructor + /// + [JsonConstructor] + public UIDStringOrderConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public UIDStringOrderConstraint(Order order, OrderedString value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Runs the ordering check on the given dataset + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as UIDStringOrderConstraint); + } + + /// + public bool Equals(UIDStringOrderConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer>.Default.Equals(Function, other.Function); + } + + /// + public override int GetHashCode() + { + var hashCode = 426273644; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Function); + return hashCode; + } + + /// + public static bool operator ==(UIDStringOrderConstraint left, UIDStringOrderConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(UIDStringOrderConstraint left, UIDStringOrderConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RegexConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RegexConstraint.cs new file mode 100644 index 0000000..784c2a2 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RegexConstraint.cs @@ -0,0 +1,122 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using System.Text.RegularExpressions; + using Dicom; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Simple regular expresession based constraint on a DicomTag that can be + /// converted to string. + /// + public class RegexConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + /// Dicom tag index. + /// Regex expression. + /// Regex options. + /// Ordinal. + [JsonConstructor] + public RegexConstraint(DicomTagIndex index, string expression, RegexOptions options = RegexOptions.None, int ordinal = 0) + : base(index) + { + Expression = expression; + Options = options; + Ordinal = ordinal; + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public RegexConstraint(DicomTag tag, string regex, RegexOptions options = RegexOptions.None, int ordinal = 0) + : this(new DicomTagIndex(tag), regex, options, ordinal) + { + } + + /// + /// The regular expression you wish to check + /// + [Required] + public string Expression { get; } + + /// + /// Options for the regex + /// + [JsonConverter(typeof(StringEnumConverter))] + [Required] + public RegexOptions Options { get; } + + /// + /// The ordinal you wish to constrain + /// + [Required] + public int Ordinal { get; } + + /// + /// Test the regular expression with the given options against a string extracted from the dicom tag specified. + /// + /// + /// If dataset is null + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + if (dataSet == null) + { + throw new ArgumentNullException(nameof(dataSet)); + } + + var r = new Regex(Expression, Options); + var s = dataSet.GetValue(Index.DicomTag, Ordinal); + + return new DicomConstraintResult(r.IsMatch(s), this); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as RegexConstraint); + } + + /// + public bool Equals(RegexConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + Expression == other.Expression && + Options == other.Options && + Ordinal == other.Ordinal; + } + + /// + public override int GetHashCode() + { + var hashCode = 1223269981; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Expression); + hashCode = hashCode * -1521134295 + Options.GetHashCode(); + hashCode = hashCode * -1521134295 + Ordinal.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(RegexConstraint left, RegexConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(RegexConstraint left, RegexConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RequiredTagConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RequiredTagConstraint.cs new file mode 100644 index 0000000..b7886c4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/RequiredTagConstraint.cs @@ -0,0 +1,154 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Encodes the requirement on a tag existing in a dataset + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue", Justification = "TBD")] + public enum TagRequirement : int + { + /// + /// The tag must be present and the conditions on the tag must pass + /// This would typically be a Type 1 Tag + /// + PresentNotEmpty = 1, + + /// + /// The tag must be present and the conditions must pass when the tag is non-empty + /// This would typically be a Type 2 Tag + /// + PresentCanBeEmpty = 2, + + /// + /// The tag does not need to be present but the condition must pass if the tag is present and non-empty + /// This would typically be a Type 3 Tag + /// + Optional = 3, + } + + /// + /// RequiredTagConstraint + /// + public class RequiredTagConstraint : DicomConstraint, IEquatable + { + /// + /// Constructor + /// + /// + /// + [JsonConstructor] + public RequiredTagConstraint(TagRequirement requirementLevel, DicomTagConstraint constraint) + { + RequirementLevel = requirementLevel; + Constraint = constraint; + } + + /// + /// Requirement for the presence of this tag + /// + [JsonConverter(typeof(StringEnumConverter))] + [Required] + public TagRequirement RequirementLevel { get; } + + /// + /// The constraint to apply on the tag value + /// + [Required] + public DicomTagConstraint Constraint { get; } + + /// + /// Inspect the given dataset for the specified tag {group:index} if the tag is required to be there + /// (RequirementLevel) apply Constraint + /// + /// + /// If dataset is null + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + if (dataSet == null) + { + throw new ArgumentNullException(nameof(dataSet)); + } + + var tag = Constraint.Index.DicomTag; + + if (dataSet.Contains(tag)) + { + // Line was if (!string.IsNullOrEmpty(dataSet.Get(tag, string.Empty))) before conversion to new OSS fo-dicom + if (!string.IsNullOrEmpty(dataSet.GetString(tag))) + { + var childResult = Constraint.Check(dataSet); + + return new DicomConstraintResult(childResult.Result, this, childResult); + } + else + { + if (RequirementLevel < TagRequirement.PresentCanBeEmpty) + { + // Must be there and non-empty + return new DicomConstraintResult(false, this); + } + else + { + // Can be empty and is empty + return new DicomConstraintResult(true, this); + } + } + } + else + { + if (RequirementLevel < TagRequirement.Optional) + { + // must be there is not + return new DicomConstraintResult(false, this); + } + else + { + // Optional and !in the dataset. + return new DicomConstraintResult(true, this); + } + } + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as RequiredTagConstraint); + } + + /// + public bool Equals(RequiredTagConstraint other) + { + return other != null && + RequirementLevel == other.RequirementLevel && + EqualityComparer.Default.Equals(Constraint, other.Constraint); + } + + /// + public override int GetHashCode() + { + var hashCode = 1394406664; + hashCode = hashCode * -1521134295 + RequirementLevel.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Constraint); + return hashCode; + } + + /// + public static bool operator ==(RequiredTagConstraint left, RequiredTagConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(RequiredTagConstraint left, RequiredTagConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/StringContainsConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/StringContainsConstraint.cs new file mode 100644 index 0000000..aae1c9c --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/StringContainsConstraint.cs @@ -0,0 +1,103 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// Insist a string contains a certain value + /// + public class StringContainsConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + /// + /// + /// + [JsonConstructor] + public StringContainsConstraint(DicomTagIndex index, string match, int ordinal = 0) + : base(index) + { + Match = match ?? throw new ArgumentNullException(nameof(match)); + Ordinal = ordinal; + } + + /// + /// Constructor + /// + public StringContainsConstraint(DicomTag tag, string match, int ordinal = 0) + : this(new DicomTagIndex(tag), match, ordinal) + { + } + + /// + /// The string that must be present in the tag + /// + [Required] + public string Match { get; } + + /// + /// The Ordinal you wish to constrain. + /// + [Required] + public int Ordinal { get; } + + /// + /// Checks the Dicom tag contains the given string + /// + /// If dataset is null + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + if (dataSet == null) + { + throw new ArgumentNullException(nameof(dataSet)); + } + + var v = dataSet.GetValue(Index.DicomTag, Ordinal); + + return new DicomConstraintResult(v.Contains(Match), this); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as StringContainsConstraint); + } + + /// + public bool Equals(StringContainsConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + Match == other.Match && + Ordinal == other.Ordinal; + } + + /// + public override int GetHashCode() + { + var hashCode = -405970557; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Match); + hashCode = hashCode * -1521134295 + Ordinal.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(StringContainsConstraint left, StringContainsConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(StringContainsConstraint left, StringContainsConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/TimeOrderConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/TimeOrderConstraint.cs new file mode 100644 index 0000000..830a7a7 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Constraints/TimeOrderConstraint.cs @@ -0,0 +1,88 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.Collections.Generic; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// An IDicomConstraint for tags of TM value representation + /// + public class TimeOrderConstraint : DicomTagConstraint, IEquatable + { + /// + /// Constructor + /// + [JsonConstructor] + public TimeOrderConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Constructor + /// + /// + /// + /// + /// + public TimeOrderConstraint(Order order, TimeSpan value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters. + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Checks that the tag in the given dataset satiisfies the ordering function + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as TimeOrderConstraint); + } + + /// + public bool Equals(TimeOrderConstraint other) + { + return other != null && + EqualityComparer.Default.Equals(Index, other.Index) && + EqualityComparer>.Default.Equals(Function, other.Function); + } + + /// + public override int GetHashCode() + { + var hashCode = 426273644; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Index); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(Function); + return hashCode; + } + + /// + public static bool operator ==(TimeOrderConstraint left, TimeOrderConstraint right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(TimeOrderConstraint left, TimeOrderConstraint right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/BaseOrderConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/BaseOrderConstraint.cs new file mode 100644 index 0000000..9251375 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/BaseOrderConstraint.cs @@ -0,0 +1,54 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using Dicom; + + /// + /// static class to compute a constraint value + /// + /// + /// + /// + internal static class BaseOrderConstraint + where TCompare : IComparable, new() + where TSelector : ISelector, new() + { + /// + /// Runs the ordering constraint + /// + /// + /// + /// + /// + /// + /// + /// + public static DicomConstraintResult Check(DicomDataset dataSet, Order order, TCompare v, DicomTag t, DicomConstraint constraint, int ordinal = 0) + { + if (dataSet == null) + { + throw new ArgumentNullException(nameof(dataSet)); + } + + var selector = new TSelector(); + + try + { + // Get will throw if the tag is not there. + var datasetValue = dataSet.GetValue(t, ordinal); + + // CompareTo is < 0 | 0 | > 0 + int r = v.CompareTo(selector.SelectValue(datasetValue)); + r = (r < 0) ? 4 : (r > 0) ? 1 : 2; + + return new DicomConstraintResult((r & (int)order) != 0, constraint); + } + + // Catch if we have tried to parse the value into the wrong type and return false + catch (FormatException) + { + return new DicomConstraintResult(false, constraint); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/IInitializable.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/IInitializable.cs new file mode 100644 index 0000000..4e89b49 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/IInitializable.cs @@ -0,0 +1,15 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + /// + /// An interface for initializing 1 type from another + /// + /// + internal interface IInitializable + { + /// + /// Convert from an instance of T + /// + /// + void Init(T source); + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/ISelector.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/ISelector.cs new file mode 100644 index 0000000..2d2431b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/ISelector.cs @@ -0,0 +1,17 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + /// + /// Interface to select an element TSelection from an instance of type TSource + /// + /// + /// + internal interface ISelector + { + /// + /// Select a value + /// + /// + /// + TSelection SelectValue(TSource source); + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/OrderingConstraint.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/OrderingConstraint.cs new file mode 100644 index 0000000..72d96b3 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/OrderingConstraint.cs @@ -0,0 +1,63 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using System.ComponentModel.DataAnnotations; + using Dicom; + using Newtonsoft.Json; + + /// + /// The ordering constraint takes a Dicom tag and an "ordering" map from the tag value to the template type + /// T and exposes an ordering constraint through DicomTagConstraint. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "TBD")] + internal class OrderingConstraint : DicomTagConstraint + where T : IComparable, new() + { + /// + /// Initializes a new instance of the class. + /// This constructor is for json serialization only. Use the alternative construtor. + /// + /// The Dicmo tag index you wish to constrain by the function. + /// The constraint function. + /// function + [JsonConstructor] + public OrderingConstraint(DicomTagIndex index, DicomOrderedTag function) + : base(index) + { + Function = function ?? throw new ArgumentNullException(nameof(function)); + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// It must be possible to extract type T from the given tag. If not an exception will be + /// thrown during a call to Check(...) + /// + /// The ordering constraint you wish to impose. + /// The value to use in the right hand side of the ordering constraint. + /// The tag you wish to constrain. + /// The ordinal index of the tag value you wish to constrain (for multi-value tag types). + public OrderingConstraint(Order order, T value, DicomTag tag, int ordinal = 0) + : this(new DicomTagIndex(tag), new DicomOrderedTag(order, value, ordinal)) + { + } + + /// + /// Ordering function parameters + /// + [Required] + public DicomOrderedTag Function { get; } + + /// + /// Checks that the tag in the given dataset satiisfies the ordering function + /// + /// + /// + public override DicomConstraintResult Check(DicomDataset dataSet) + { + return BaseOrderConstraint>. + Check(dataSet, Function.Order, Function.Value, Index.DicomTag, this, Function.Ordinal); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/Selectors.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/Selectors.cs new file mode 100644 index 0000000..74d7ed2 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Implementation/Selectors.cs @@ -0,0 +1,83 @@ +namespace Microsoft.InnerEye.DicomConstraints +{ + using System; + using Dicom; + + /// + /// Default selector - selects the source + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "TBD")] +#pragma warning disable SA1649 + internal class DefaultSelector : ISelector +#pragma warning restore SA1649 + { + /// + /// Constructor + /// + /// + /// + public T SelectValue(T source) + { + return source; + } + } + + /// + /// A generic ISelector implementation that uses IInitializable to convert from TSource to TSelection + /// + /// + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "TBD")] + internal class ConvertSelector : ISelector + where TSelection : IInitializable, new() + { + /// + /// some more + /// + /// + /// + public TSelection SelectValue(TSource source) + { + var ty = new TSelection(); + ty.Init(source); + return ty; + } + } + + /// + /// Selects the timeofday from a DateTime instance + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "TBD")] + internal class TimeSelector + : ISelector + { + /// + public TimeSpan SelectValue(DateTime source) + { + return source.TimeOfDay; + } + } + + /// + /// Extracts an ordered string for comparison from a DicomUID + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "TBD")] + internal class OrderedUIDStringSelector + : ISelector + { + /// + /// Return the DicomUID as an OrderedString + /// + /// + /// + public OrderedString SelectValue(DicomUID source) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + return source.UID; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Microsoft.InnerEye.DicomConstraints.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Microsoft.InnerEye.DicomConstraints.csproj new file mode 100644 index 0000000..18f554b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.DicomConstraints/Microsoft.InnerEye.DicomConstraints.csproj @@ -0,0 +1,30 @@ + + + net462 + x64 + Microsoft.InnerEye.DicomConstraints + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Constraints for DICOM datasets for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/AssociationStatus.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/AssociationStatus.cs new file mode 100644 index 0000000..ee3c8b3 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/AssociationStatus.cs @@ -0,0 +1,183 @@ +namespace Microsoft.InnerEye.Gateway.Logging +{ + /// + /// The DICOM association status logging enumeration. + /// + public enum AssociationStatus + { + /// + /// The Gateway has received a Dicom echo request. + /// + DicomEcho, + + /// + /// The Dicom association has closed. + /// + DicomAssociationClosed, + + /// + /// A Dicom association has opened. + /// + DicomAssociationOpened, + + /// + /// File received. + /// + FileReceived, + + /// + /// The result of a check for patient consent. + /// + ConsentCheck, + + /// + /// Files found that have been consented and can be uploaded for machine learning training. + /// + ConsentedUploadedFiles, + + /// + /// Error enqueuing message. + /// + BaseEnqueueMessageError, + + /// + /// Error enumerating directory. + /// + BaseEnumerateDirectoryError, + + /// + /// Error opening DICOM file. + /// + BaseOpenDicomFileError, + + /// + /// Uploading data to the API. + /// + Uploading, + + /// + /// Finished uploading data to the API. + /// + Uploaded, + + /// + /// Process an upload queue item. + /// + UploadProcessQueueItem, + + /// + /// Error uploading data to the API. + /// + UploadError, + + /// + /// Error in upload, the association folder has been deleted. + /// + UploadErrorAssocationFolderDeleted, + + /// + /// Error in upload, no configuration matches the SOP class. + /// + UploadErrorMissingSopClassUploadConfiguration, + + /// + /// Error in upload, tags do not match. + /// + UploadErrorTagsDoNotMatch, + + /// + /// Error in upload, destination is null. + /// + UploadErrorDestinationEmpty, + + /// + /// Error in upload, DICOM endpoint is not valid. + /// + UploadErrorDicomEndpointInvalid, + + /// + /// Attempting to download data from the API. + /// + Downloading, + + /// + /// Finished downloading data from the API. + /// + Downloaded, + + /// + /// Error downloading data. + /// + DownloadError, + + /// + /// Attempting a DICOM push for the downloaded result file. + /// + Pushing, + + /// + /// DICOM push finished for the downloaded result file. + /// + Pushed, + + /// + /// Error during DICOM push. + /// + PushError, + + /// + /// Error, cannot push because applicationEntityConfig.Destination is null. + /// + PushErrorDestinationEmpty, + + /// + /// Error in push, no files to push. + /// + PushErrorFilesEmpty, + + /// + /// Delete path (either a directory or file). + /// + DeletePath, + + /// + /// Deleted data from disk. + /// + Deleted, + + /// + /// Error deleting data from disk. + /// + DeleteError, + + /// + /// Posted the feedback structure set file. + /// + PostedFeedback, + + /// + /// Uploaded DICOM series data for long term storage. + /// + UploadedDicomSeriesData, + + /// + /// Error uploading DICOM series data. + /// + UploadDicomSeriesDataError, + + /// + /// Receive service is starting. + /// + ReceiveServiceStart, + + /// + /// An exception caught when enqueuing message. + /// + ReceiveEnqueueError, + + /// + /// Error in receive, cannot add to upload queue. + /// + ReceiveUploadError, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntry.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntry.cs new file mode 100644 index 0000000..a9e9383 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntry.cs @@ -0,0 +1,389 @@ +namespace Microsoft.InnerEye.Gateway.Logging +{ + using System; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Models; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + /// + /// Gateway log item. + /// + /// + /// This holds all properties that may be logged, but it is expected that in any instance + /// almost all of them will be null. + /// + public class LogEntry + { + /// + /// Log entry type, . + /// + public LogEntryType LogEntryType { get; } + + /// + /// Freeform additional information about log entry. + /// + public string Information { get; } + + /// + /// Assocation status, populated if LogEntryType == LogEntryType.AssociationStatus. + /// + /// + public AssociationStatus? AssociationStatus { get; } + + /// + /// Message queue status, populated if LogEntryType == LogEntryType.MessageQueueStatus. + /// + /// + public MessageQueueStatus? MessageQueueStatus { get; } + + /// + /// Service status, populated if LogEntryType == LogEntryType.ServiceStatus. + /// + /// + public ServiceStatus? ServiceStatus { get; } + + /// + /// DICOM association unique identifier, . + /// + public Guid? AssociationGuid { get; } + + /// + /// Message queue dequeue count, + /// + public int? MessageQueueDequeueCount { get; } + + /// + /// DICOM association start time, . + /// + public DateTime? AssociationDateTime { get; } + + /// + /// Elapsed milliseconds, i.e. now - AssociationDateTime, in milliseconds. + /// + public double? ElapsedMilliseconds { get; } + + /// + /// DICOM association calling application entity title, . + /// + public string CallingApplicationEntityTitle { get; } + + /// + /// DICOM association called application entity title, . + /// + public string CalledApplicationEntityTitle { get; } + + /// + /// Download segmentation identifier. Created during upload and copied to . + /// + public string SegmentationId { get; } + + /// + /// Segmentation model identifier, . + /// + public string ModelId { get; } + + /// + /// Segmentation download progress, from ModelResult.Progress (not imported here). + /// + public int? DownloadProgress { get; } + + /// + /// Segmentation download error, from ModelResult.Error (not imported here). + /// + public string DownloadError { get; } + + /// + /// Path to directory or file, will be one of: or + /// or a file in the folder . + /// + public string Path { get; } + + /// + /// Destination message queue path for moving a message between queues. + /// + public string DestinationMessageQueuePath { get; } + + /// + /// Source message queue path for handling messages. + /// + public string SourceMessageQueuePath { get; } + + /// + /// Destination IP address, or DicomEndPoint.Ip (not imported here). + /// + public string DestinationIp { get; } + + /// + /// Destination title, or DicomEndPoint.Title (not imported here). + /// + public string DestinationTitle { get; } + + /// + /// Destination port, or DicomEndPoint.Port (not imported here). + /// + public int? DestinationPort { get; } + + /// + /// DICOM tags that did not match during upload. + /// + public string FailedTags { get; } + + /// + /// DicomReceiveProgressCode from DicomDataReceiverProgressEventArgs (not imported here). + /// + public object ProgressCode { get; } + + /// + /// DicomAssociation.RemoteHost from DicomDataReceiverProgressEventArgs (not imported here). + /// + public string CallingIp { get; } + + /// + /// DicomAssociation.RemotePort from DicomDataReceiverProgressEventArgs (not imported here). + /// + public int? CallingPort { get; } + + /// + /// DicomAssociation.RemoteImplementationClassUID?.UID from DicomDataReceiverProgressEventArgs (not imported here). + /// + public string RemoteImplementationClassUID { get; } + + /// + /// DicomAssociation.RemoteImplementationVersion from DicomDataReceiverProgressEventArgs (not imported here). + /// + public string RemoteImplementationVersion { get; } + + /// + /// Formatted DicomAssociation.PresentationContexts from DicomDataReceiverProgressEventArgs (not imported here). + /// + public string PresentationContexts { get; } + + /// + /// Shorthand for creating an initialize log entry. + /// + /// New LogEntry of type Initialize. + public static LogEntry CreateInitialize() => + new LogEntry(Logging.LogEntryType.Initialize); + + /// + /// Shorthand for creating an association status log entry. + /// + /// Association status. + /// Freeform information about log item. + /// Queue item base. + /// Delete queue item. + /// Download queue item. + /// Download progress. + /// Download error. + /// Push queue item. + /// Upload queue item. + /// Segmentation id. + /// Model id. + /// Destination. + /// Path. + /// String formatted list of failed DICOM tags. + /// Receiver progress. + /// New LogEntry of type SegmentationStatus. + public static LogEntry Create( + AssociationStatus associationStatus, + string information = null, + QueueItemBase queueItemBase = null, + DeleteQueueItem deleteQueueItem = null, + DownloadQueueItem downloadQueueItem = null, + int? downloadProgress = null, + string downloadError = null, + PushQueueItem pushQueueItem = null, + UploadQueueItem uploadQueueItem = null, + string segmentationId = null, + string modelId = null, + (string ipAddress, string title, int port)? destination = null, + string path = null, + string failedDicomTags = null, + (object progressCode, string remoteHost, int remotePort, string uid, string version, string logPresentation)? dicomDataReceiverProgress = null) => + new LogEntry( + Logging.LogEntryType.AssociationStatus, + information: information, + associationStatus: associationStatus, + queueItemBase: queueItemBase, + deleteQueueItem: deleteQueueItem, + downloadQueueItem: downloadQueueItem, + downloadProgress: downloadProgress, + downloadError: downloadError, + pushQueueItem: pushQueueItem, + uploadQueueItem: uploadQueueItem, + segmentationId: segmentationId, + modelId: modelId, + destination: destination, + path: path, + failedDicomTags: failedDicomTags, + dicomDataReceiverProgress: dicomDataReceiverProgress); + + /// + /// Shorthand for creating a service status log entry. + /// + /// Service status. + /// Freeform information about log item. + /// New LogEntry of type ServiceStatus. + public static LogEntry Create( + ServiceStatus serviceStatus, + string information = null) => + new LogEntry(Logging.LogEntryType.ServiceStatus, information: information, serviceStatus: serviceStatus); + + /// + /// Shorthand for creating a message queue status log entry. + /// + /// Message queue status. + /// Freeform information about log item. + /// Queue item base. + /// Destination message queue path. + /// Source message queue path. + /// New LogEntry of type MessageQueueStatus. + public static LogEntry Create( + MessageQueueStatus messageQueueStatus, + string information = null, + QueueItemBase queueItemBase = null, + string destinationMessageQueuePath = null, + string sourceMessageQueuePath = null) => + new LogEntry( + Logging.LogEntryType.MessageQueueStatus, + information: information, + messageQueueStatus: messageQueueStatus, + queueItemBase: queueItemBase, + destinationMessageQueuePath: destinationMessageQueuePath, + sourceMessageQueuePath: sourceMessageQueuePath); + + /// + /// Creates a new LogEntry. + /// + /// Log entry type. + /// Freeform information about log item. + /// Association status. + /// Message queue status. + /// Service status. + /// Queue item base if derived type not known. + /// Delete queue item. + /// Download queue item. + /// Download progress. + /// Download error. + /// Push queue item. + /// Upload queue item. + /// Destination message queue path. + /// Source message queue path. + /// Segmentation id. + /// Model id. + /// Destination. + /// Path. + /// String formatted list of failed DICOM tags. + /// Receiver progress. + public LogEntry( + LogEntryType logEntryType, + string information = null, + AssociationStatus? associationStatus = null, + MessageQueueStatus? messageQueueStatus = null, + ServiceStatus? serviceStatus = null, + QueueItemBase queueItemBase = null, + DeleteQueueItem deleteQueueItem = null, + DownloadQueueItem downloadQueueItem = null, + int? downloadProgress = null, + string downloadError = null, + PushQueueItem pushQueueItem = null, + UploadQueueItem uploadQueueItem = null, + string destinationMessageQueuePath = null, + string sourceMessageQueuePath = null, + string segmentationId = null, + string modelId = null, + (string ipAddress, string title, int port)? destination = null, + string path = null, + string failedDicomTags = null, + (object progressCode, string remoteHost, int remotePort, string uid, string version, string logPresentation)? dicomDataReceiverProgress = null) + { + LogEntryType = logEntryType; + Information = information; + AssociationStatus = associationStatus; + MessageQueueStatus = messageQueueStatus; + ServiceStatus = serviceStatus; + + var someAssociationQueueItemBase = deleteQueueItem ?? downloadQueueItem ?? (AssociationQueueItemBase)pushQueueItem ?? uploadQueueItem; + var someQueueItemBase = queueItemBase ?? someAssociationQueueItemBase; + + if (someQueueItemBase != null) + { + AssociationGuid = someQueueItemBase.AssociationGuid; + MessageQueueDequeueCount = someQueueItemBase.DequeueCount; + AssociationDateTime = someQueueItemBase.AssociationDateTime; + ElapsedMilliseconds = (DateTime.UtcNow - someQueueItemBase.AssociationDateTime).TotalMilliseconds; + } + + if (someAssociationQueueItemBase != null) + { + CallingApplicationEntityTitle = someAssociationQueueItemBase.CallingApplicationEntityTitle; + CalledApplicationEntityTitle = someAssociationQueueItemBase.CalledApplicationEntityTitle; + } + + SegmentationId = downloadQueueItem?.SegmentationID ?? segmentationId; + ModelId = downloadQueueItem?.ModelId ?? modelId; + DownloadProgress = downloadProgress; + DownloadError = downloadError; + Path = uploadQueueItem?.AssociationFolderPath ?? path; + DestinationMessageQueuePath = destinationMessageQueuePath; + SourceMessageQueuePath = sourceMessageQueuePath; + + if (destination.HasValue) + { + var dest = destination.Value; + + DestinationIp = dest.ipAddress; + DestinationTitle = dest.title; + DestinationPort = dest.port; + } + + FailedTags = failedDicomTags; + + if (dicomDataReceiverProgress.HasValue) + { + var progress = dicomDataReceiverProgress.Value; + + ProgressCode = progress.progressCode; + CallingIp = progress.remoteHost; + CallingPort = progress.remotePort; + RemoteImplementationClassUID = progress.uid; + RemoteImplementationVersion = progress.version; + PresentationContexts = progress.logPresentation; + } + } + + /// + /// Log an event and optional exception. + /// + /// Logger. + /// Log level. + /// Optional exception. + public void Log(ILogger logger, Microsoft.Extensions.Logging.LogLevel logLevel, Exception exception = null) => + logger?.Log(logLevel, ToEventId(), this, exception, LogEntryFormatter); + + /// + /// Create an EventId for a LogEntryType. + /// + /// New EventId. + private EventId ToEventId() => new EventId((int)LogEntryType, LogEntryType.ToString()); + + /// + /// Format LogEntry and optional exception as string. + /// + /// Log entry. + /// Exception. + /// JSON stringified log entry. + private static string LogEntryFormatter(LogEntry logEntry, Exception exception) + { + var serializerSettings = new JsonSerializerSettings() + { + Converters = new[] { new StringEnumConverter() }, + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }; + + return JsonConvert.SerializeObject(logEntry, serializerSettings); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntryType.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntryType.cs new file mode 100644 index 0000000..2b32ab8 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/LogEntryType.cs @@ -0,0 +1,34 @@ +namespace Microsoft.InnerEye.Gateway.Logging +{ + /// + /// Type of LogEntry. + /// + public enum LogEntryType + { + /// + /// Log entry indicates ConfigurationService is starting. + /// + Initialize, + + /// + /// Log entry indicates status of a DICOM association. + /// The field will be populated. + /// + /// + AssociationStatus, + + /// + /// Log entry indicates status of a service. + /// The field will be populated. + /// + /// + ServiceStatus, + + /// + /// Log entry indicated a message queue status. + /// The field will be populated. + /// + /// + MessageQueueStatus + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/MessageQueueStatus.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/MessageQueueStatus.cs new file mode 100644 index 0000000..9d3e7d4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/MessageQueueStatus.cs @@ -0,0 +1,73 @@ +namespace Microsoft.InnerEye.Gateway.Logging +{ + /// + /// The message queue item logging enumeration. + /// + public enum MessageQueueStatus + { + /// + /// Initialised a message queue. + /// + InitialisedQueue, + + /// + /// Error initialising message queue. + /// + InitialiseQueueError, + + /// + /// Enqueued a message. + /// + Enqueued, + + /// + /// Error enqueuing message. + /// + EnqueueError, + + /// + /// Dequeued a message. + /// + Dequeued, + + /// + /// Error dequeuing a message. + /// + DequeueError, + + /// + /// Enqueued a message onto the dead letter queue. + /// + EnqueuedDeadLetter, + + /// + /// Message too old to be put on dead letter queue. + /// + TooOldForDeadLetterError, + + /// + /// Moved a message from one queue to another. + /// + Moved, + + /// + /// Error moving message from one queue to another. + /// + MoveError, + + /// + /// Error beginning transaction. + /// + BeginTransactionError, + + /// + /// Error in handling a transaction exception. + /// + TransactionExceptionHandlerError, + + /// + /// Error in Dispose. + /// + DisposeError, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/Microsoft.InnerEye.Gateway.Logging.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/Microsoft.InnerEye.Gateway.Logging.csproj new file mode 100644 index 0000000..bf40323 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/Microsoft.InnerEye.Gateway.Logging.csproj @@ -0,0 +1,30 @@ + + + net462 + x64 + Microsoft.InnerEye.Gateway.Logging + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Logging for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/ServiceStatus.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/ServiceStatus.cs new file mode 100644 index 0000000..be9be32 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Logging/ServiceStatus.cs @@ -0,0 +1,74 @@ +namespace Microsoft.InnerEye.Gateway.Logging +{ + /// + /// The service status logging enumeration. + /// + public enum ServiceStatus + { + /// + /// Service starting up. + /// + Starting, + + /// + /// Service started. + /// + Started, + + /// + /// Error starting service. + /// + StartError, + + /// + /// Service stopping. + /// + Stopping, + + /// + /// Service stopped. + /// + Stopped, + + /// + /// Error stopping service. + /// + StoppingError, + + /// + /// Service is updating. + /// + Updating, + + /// + /// A new configuration for the service has been detected and is + /// ready to be applied. + /// + NewConfigurationAvailable, + + /// + /// A new configuration has been applied to the services. + /// + NewConfigurationApplied, + + /// + /// Error loading new configuration. + /// + NewConfigurationError, + + /// + /// Error in ping. + /// + PingError, + + /// + /// Error in execute. + /// + ExecuteError, + + /// + /// Error in GetAcceptedSopClassesAndTransferSyntaxes. + /// + GetAcceptedSopClassesAndTransferSyntaxesError, + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueuePermissionsException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueuePermissionsException.cs new file mode 100644 index 0000000..e913433 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueuePermissionsException.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception class for any message queue permission errors e.g. no permission to read from a queue + /// + [Serializable] + public class MessageQueuePermissionsException : Exception + { + /// + /// Prevents a default instance of the class from being created. + /// + private MessageQueuePermissionsException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MessageQueuePermissionsException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public MessageQueuePermissionsException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected MessageQueuePermissionsException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueReadException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueReadException.cs new file mode 100644 index 0000000..3dabea8 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueReadException.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception class for any exceptions during a read from a message queue. + /// + [Serializable] + public class MessageQueueReadException : Exception + { + /// + /// Prevents a default instance of the class from being created. + /// + private MessageQueueReadException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MessageQueueReadException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public MessageQueueReadException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected MessageQueueReadException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueTransactionBeginException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueTransactionBeginException.cs new file mode 100644 index 0000000..18e1407 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueTransactionBeginException.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception class for any exceptions during a transaction begin method. + /// + [Serializable] + public class MessageQueueTransactionBeginException : Exception + { + /// + /// Prevents a default instance of the class from being created. + /// + private MessageQueueTransactionBeginException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MessageQueueTransactionBeginException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public MessageQueueTransactionBeginException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected MessageQueueTransactionBeginException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueWriteException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueWriteException.cs new file mode 100644 index 0000000..1095493 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Exceptions/MessageQueueWriteException.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception class for any exceptions during a write to a message queue. + /// + [Serializable] + public class MessageQueueWriteException : Exception + { + /// + /// Prevents a default instance of the class from being created. + /// + private MessageQueueWriteException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public MessageQueueWriteException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public MessageQueueWriteException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected MessageQueueWriteException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/GatewayMessageQueue.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/GatewayMessageQueue.cs new file mode 100644 index 0000000..2116c1f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/GatewayMessageQueue.cs @@ -0,0 +1,37 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing +{ + using Microsoft.InnerEye.Gateway.MessageQueueing.Sqlite; + + /// + /// Message queue constants and creator. + /// + public static class GatewayMessageQueue + { + /// + /// The upload queue path. + /// + public const string UploadQueuePath = @".\Private$\ListenerUploadQueue"; + + /// + /// The download queue path. + /// + public const string DownloadQueuePath = @".\Private$\ListenerDownloadQueue"; + + /// + /// The push queue path. + /// + public const string PushQueuePath = @".\Private$\ListenerPushQueue"; + + /// + /// The delete queue path + /// + public const string DeleteQueuePath = @".\Private$\DeleteQueue"; + + /// + /// Gets a new message queue instance. + /// + /// The message queue path. + /// The message queue interface. + public static IMessageQueue Get(string path) => new SqliteMessageQueue(path); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IMessageQueue.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IMessageQueue.cs new file mode 100644 index 0000000..91f850f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IMessageQueue.cs @@ -0,0 +1,44 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing +{ + using System; + + /// + /// The message queue interface. + /// + public interface IMessageQueue : IDisposable + { + /// + /// Gets the queue path. + /// + /// + /// The queue path. + /// + string QueuePath { get; } + + /// + /// Clear the entire queue of all messages. + /// + void Clear(); + + /// + /// Creates a new queue transaction. + /// + /// The queue transaction. + IQueueTransaction CreateQueueTransaction(); + + /// + /// Dequeues the next message. + /// + /// The queue transaction. + /// The next message on the queue. + T DequeueNextMessage(IQueueTransaction queueTransaction); + + /// + /// Enqueues the specified message. + /// + /// The enqueue type. + /// The value. + /// The queue transaction. + void Enqueue(T value, IQueueTransaction queueTransaction); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IQueueTransaction.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IQueueTransaction.cs new file mode 100644 index 0000000..56872e4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/IQueueTransaction.cs @@ -0,0 +1,26 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing +{ + using System; + + /// + /// A queue transaction interface. + /// + public interface IQueueTransaction : IDisposable + { + /// + /// Begins the queue transaction. + /// + /// Failed to begin the transaction. + void Begin(); + + /// + /// Commits the transaction. + /// + void Commit(); + + /// + /// Aborts the transaction. + /// + void Abort(); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Microsoft.InnerEye.Gateway.MessageQueueing.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Microsoft.InnerEye.Gateway.MessageQueueing.csproj new file mode 100644 index 0000000..e78328b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Microsoft.InnerEye.Gateway.MessageQueueing.csproj @@ -0,0 +1,25 @@ + + + net462 + x64 + Microsoft.InnerEye.Gateway.MessageQueueing + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Transactional message queueing implementations for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueue.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueue.cs new file mode 100644 index 0000000..b0f0385 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueue.cs @@ -0,0 +1,320 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing.Sqlite +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + + using Newtonsoft.Json; + + using Microsoft.InnerEye.Gateway.Sqlite; + using Microsoft.InnerEye.Gateway.Sqlite.Extensions; + using InnerEye.Gateway.MessageQueueing.Exceptions; + + /// + /// Message queue service using SQLite as the backend database. + /// + public class SqliteMessageQueue : IMessageQueue + { + /// + /// The database connection path. + /// Note: To explore the database file you can use the tool: https://sqlitebrowser.org/ + /// + private const string DatabaseConnectionStringFormat = @"Data Source={0}\MicrosoftInnerEyeGatewayMessageQueue.db;"; + + /// + /// The data column (name & data type). + /// + private static readonly (string ColumnName, string ColumnDataType) QueueTableDataColumn = ("data", "TEXT"); + + /// + /// The transaction identifier column (name & data type). + /// + private static readonly (string ColumnName, string ColumnDataType) QueueTableTransactionIdColumn = ("transactionId", "VARCHAR(36)"); + + /// + /// The transaction lease column (name & data type). + /// + private static readonly (string ColumnName, string ColumnDataType) QueueTableTransactionLeaseColumn = ("transactionLease", "INT"); + + /// + /// The row unique identifier column (name & data type). + /// + private static readonly (string ColumnName, string ColumnDataType) QueueTableRowIdColumn = ("rowId", "VARCHAR(36)"); + + /// + /// The enqueue row identifier column (name & data type). + /// + private static readonly (string ColumnName, string ColumnDataType) QueueTableEnqueueRowIdColumn = ("enqueueRowId", "VARCHAR(36)"); + + /// + /// The row enqueue time column (name & data type). + /// + private static readonly (string ColumnName, string ColumnDataType) QueueTableEnqueueTimeColumn = ("enqueueTime", "INT"); + + /// + /// The queue table data column names and data type. + /// + private static readonly (string ColumnName, string ColumnDataType)[] QueueTableDataColumnNames = new (string, string)[] + { + QueueTableDataColumn, + QueueTableTransactionIdColumn, + QueueTableTransactionLeaseColumn, + QueueTableRowIdColumn, + QueueTableEnqueueRowIdColumn, + QueueTableEnqueueTimeColumn + }; + + /// + /// SqliteManager object if Logger config is set to SQLite + /// + public SqliteManager SqliteManager { get; set; } + /// + /// The lease of a transaction in milliseconds. + /// + private readonly uint _transactionLeaseMs; + + /// + /// The delay in milliseconds the renew lease task will attempt to renew the lease of a row. + /// + private readonly uint _transactionRenewLeaseMs; + + /// + /// The connection string to the database. + /// + private readonly string _databaseConnectionString; + + /// + /// The table name. + /// + private readonly string _tableName; + + /// + /// If this instance is disposed. + /// + private bool _disposed; + + /// + /// Initializes a new instance of the class. + /// + /// The table name. + /// + /// The lease of a transaction in milliseconds (Default is 1 minute, which will be renewed every 30 seconds). + /// The value must be larger than 1 second to allow the renew task time to renew a rows lease. + /// + /// If the table name is null or whitespace or the transaction lease is less than 1 second. + /// If we fail to open a connection to the database. + public SqliteMessageQueue(string tableName, uint transactionLeaseMs = 60 * 1000) + { + _tableName = !string.IsNullOrWhiteSpace(tableName) ? tableName : throw new ArgumentException(nameof(tableName)); + + _transactionLeaseMs = transactionLeaseMs >= 1000 ? transactionLeaseMs : throw new ArgumentException(nameof(transactionLeaseMs)); + _transactionRenewLeaseMs = transactionLeaseMs / 2; // Attempt to renew leases using timeout divided by 2 + + SqliteManager = new SqliteManager(tableName, DatabaseConnectionStringFormat, QueueTableDataColumnNames); + _databaseConnectionString = SqliteManager.DatabaseConnectionString; + } + + /// + /// The name of the table the SQLite message queue will write queue messages into. + /// + public string QueuePath => _tableName; + + /// + /// Gets the database connection string. + /// + public string DatabaseConnectionString => _databaseConnectionString; + + /// + public IQueueTransaction CreateQueueTransaction() + { + return new SqliteMessageQueueTransaction(_databaseConnectionString, _transactionRenewLeaseMs); + } + + /// + public void Clear() + { + SqliteExtensions.ExecuteNonQueryNewConnection( + connectionString: _databaseConnectionString, + commandText: string.Format(SqliteManager.ClearTableCommandTextFormat, QueuePath)); + } + + /// + /// If the queue transaction is null or is not the correct type. + /// If input value is null. + /// If the queue cannot write the input value. + [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")] + public void Enqueue(T value, IQueueTransaction queueTransaction) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + var transaction = queueTransaction as SqliteMessageQueueTransaction; + + if (transaction == null) + { + throw new ArgumentException(nameof(queueTransaction)); + } + + // Create a unique row ID for this item (this will also be used for the commit and abort commands) + var rowId = Guid.NewGuid(); + + using (var command = transaction.GetSqliteCommand()) + { + // Note: Make sure you call ToString() here on the row identifier + command.Parameters.AddWithValue($"@{QueueTableRowIdColumn.ColumnName}", rowId.ToString()); + // Create enqueue row identifier, that should never be touched + command.Parameters.AddWithValue($"@{QueueTableEnqueueRowIdColumn.ColumnName}", rowId.ToString()); + command.Parameters.AddWithValue($"@{QueueTableDataColumn.ColumnName}", JsonConvert.SerializeObject(value)); + command.Parameters.AddWithValue($"@{QueueTableTransactionIdColumn.ColumnName}", transaction.TransactionId); + command.Parameters.AddWithValue($"@{QueueTableTransactionLeaseColumn.ColumnName}", 0); + command.Parameters.AddWithValue($"@{QueueTableEnqueueTimeColumn.ColumnName}", DateTime.UtcNow.Ticks); + + // Execute the query and insert the row + var result = command.ExecuteNonQueryWithRetry(string.Format(SqliteManager.GetInsertRowCommandFormat(), QueuePath)); + + if (result == 0) + { + throw new MessageQueueWriteException("Failed to write to the SQLite message queue table."); + } + + // Create a new command for commit to remove the lock on this row (note: we read by enqueue row identifier) + transaction.EnqueueCommitCommand( + GetUpdateSqlCommandText( + new[] { (QueueTableTransactionIdColumn.ColumnName, string.Empty) }, // On commit, remove the transaction lock + GetWhereClauseRowIdCommandText(rowId, QueueTableEnqueueRowIdColumn.ColumnName))); + + // Create a new command when the transaction is aborted to delete the row (note: we read by enqueue row identifier). + transaction.EnqueueAbortCommand(GetDeleteRowSqlCommandText(rowId, QueueTableEnqueueRowIdColumn.ColumnName)); + } + } + + /// + /// If the queue transaction is null or is not the correct type. + /// If the queue deos not have any items on the queue. + public T DequeueNextMessage(IQueueTransaction queueTransaction) + { + var transaction = queueTransaction as SqliteMessageQueueTransaction; + + if (transaction == null) + { + throw new ArgumentException(nameof(queueTransaction)); + } + + var result = default(T); + var rowId = Guid.NewGuid(); + + using (var command = transaction.GetSqliteCommand()) + { + // Attempt to update one row by setting it as not readable by other readers. + // We set a lease on this item, and clear out any transaction IDs that might be set. + var updateResult = command.ExecuteNonQueryWithRetry( + GetUpdateSqlCommandText( + new[] + { + // Note: We change the row ID to a new ID so we know which row to read (but leave the enqueue row ID) + (QueueTableRowIdColumn.ColumnName, rowId.ToString()), + (QueueTableTransactionIdColumn.ColumnName, string.Empty), // Empty the transaction ID + (QueueTableTransactionLeaseColumn.ColumnName, DateTime.UtcNow.AddMilliseconds(_transactionLeaseMs).Ticks.ToString()) // Set a lease + }, + $"WHERE {QueueTableRowIdColumn.ColumnName} IN (SELECT {QueueTableRowIdColumn.ColumnName} FROM [{QueuePath}] WHERE ({QueueTableTransactionIdColumn.ColumnName} = \"\" AND {QueueTableTransactionLeaseColumn.ColumnName} < {DateTime.UtcNow.Ticks}) OR {QueueTableTransactionIdColumn.ColumnName} = \"{transaction.TransactionId}\" ORDER BY {QueueTableEnqueueTimeColumn.ColumnName} ASC LIMIT 1)")); + + // Check if we actually modified any rows. If not we don't have any queue messages to process. + // If we did modify a row we can read it and return the result to the caller + if (updateResult != 1) + { + throw new MessageQueueReadException($"There is nothing to read from the SQLite table: {QueuePath}."); + } + + // If we did we attempt to update any row - read the updated row directly and deserialize the result. + // Execute scalar and de-serialize the first column to the expected data type. + result = command.ExecuteScalarWithRetry(GetReadRowCommandText(rowId, QueueTableRowIdColumn.ColumnName)); + } + + // Create a new command when the transaction is commited to delete this row. + transaction.EnqueueCommitCommand(GetDeleteRowSqlCommandText(rowId, QueueTableRowIdColumn.ColumnName)); + + // Enqueue a function to renew the lease for this row on a timer task. + transaction.EnqueueRenewLeaseCommand( + () => GetUpdateSqlCommandText( + new[] { (QueueTableTransactionLeaseColumn.ColumnName, DateTime.UtcNow.AddMilliseconds(_transactionLeaseMs).Ticks.ToString()) }, + GetWhereClauseRowIdCommandText(rowId, QueueTableRowIdColumn.ColumnName))); + + // Create a new command to expire the lease if the transaction is aborted. + transaction.EnqueueAbortCommand( + GetUpdateSqlCommandText( + new[] { (QueueTableTransactionLeaseColumn.ColumnName, "0") }, + GetWhereClauseRowIdCommandText(rowId, QueueTableRowIdColumn.ColumnName))); + + return result; + } + + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Disposes of all managed resources. + /// + /// If we are disposing. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + _disposed = true; + } + + /// + /// Gets the SQLite command text to read one row using its row identifier. + /// + /// The row identifier. + /// The column name to match the row identifier with. + /// The SQLite command text. + private string GetReadRowCommandText(Guid rowId, string columnName) + { + return $"SELECT * FROM [{QueuePath}] {GetWhereClauseRowIdCommandText(rowId, columnName)}"; + } + + /// + /// Get the SQLite command text to update rows with the specified column values. + /// + /// The column name/ values tuple to set during the update. + /// The where clause to limit which rows are updated. + /// The SQLite command text. + private string GetUpdateSqlCommandText((string ColumnName, string Value)[] columnValues, string whereClause = "") + { + return $"UPDATE [{QueuePath}] " + + $"SET {string.Join(", ", columnValues.Select(x => $"{x.ColumnName} = \"{x.Value}\""))} " + + $"{(string.IsNullOrWhiteSpace(whereClause) ? string.Empty : whereClause)}"; + } + + /// + /// Gets the SQLite command text to delete a specific row. + /// + /// The unique row identifier to update. + /// The column name to match the row identifier with. + /// The SQLite command text. + private string GetDeleteRowSqlCommandText(Guid rowId, string columnName) + { + return $"DELETE FROM [{QueuePath}] {GetWhereClauseRowIdCommandText(rowId, columnName)}"; + } + + /// + /// Gets the where clause of a SQLite command text for finding a row by its identifier. + /// + /// The row identifier to limit the search on. + /// The column name to match the row identifier with. + /// The where clause SQLite command text. + private string GetWhereClauseRowIdCommandText(Guid rowId, string columnName) + { + return $"WHERE {columnName} IN (SELECT {columnName} FROM [{QueuePath}] WHERE {columnName} = \"{rowId}\" LIMIT 1)"; + } + + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueueTransaction.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueueTransaction.cs new file mode 100644 index 0000000..b81fd99 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.MessageQueueing/Sqlite/SqliteMessageQueueTransaction.cs @@ -0,0 +1,357 @@ +namespace Microsoft.InnerEye.Gateway.MessageQueueing.Sqlite +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Linq; + using System.Threading; + + using Microsoft.Data.Sqlite; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Sqlite.Extensions; + + /// + /// SQLite queue transaction implementation. + /// + public class SqliteMessageQueueTransaction : IQueueTransaction + { + /// + /// Error message when Abort or Commit is called before Begin transaction. + /// + private const string BeginNotCalledErrorExceptionMessage = "Begin has not been called yet"; + + /// + /// The lock object for adding transaction commit/ abort commands. + /// + private readonly object _lockObject = new object(); + + /// + /// The commands to run on a transaction commit. + /// + private readonly IList _commitCommandTexts = new List(); + + /// + /// The commands to run on a transaction abort. + /// + private readonly IList _abortCommandTexts = new List(); + + /// + /// The commands to run to renew the lease of modified rows for this transaction. + /// + private readonly IList> _renewLeaseCommandTexts = new List>(); + + /// + /// The SQLite connection string. + /// + private readonly string _sqliteConnectionString; + + /// + /// The time in milliseconds the transaction should attempt to renew leases. + /// + private readonly uint _transactionRenewLeaseMs; + + /// + /// If this instance is disposed. + /// + private bool _disposed = false; + + /// + /// The current SQLite connection for this transaction. + /// + private SqliteConnection _sqliteConnection; + + /// + /// The renew lease timer (or null if no transaction has started). + /// + private Timer _renewLeaseTimer; + + /// + /// Initializes a new instance of the class. + /// + /// The SQLite connection string. + /// The time in milliseconds the transaction should attempt to renew leases. + public SqliteMessageQueueTransaction(string sqlConnectionString, uint transactionRenewLeaseMs) + { + _sqliteConnectionString = sqlConnectionString; + _transactionRenewLeaseMs = transactionRenewLeaseMs; + + TransactionId = Guid.NewGuid().ToString(); + } + + /// + /// The unqiue identifier for this transaction. + /// + internal string TransactionId { get; } + + /// + /// When this method is called before begin. + /// If this instance is already disposed. + public void Abort() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(SqliteMessageQueueTransaction)); + } + + if (_sqliteConnection == null) + { + throw new InvalidOperationException(BeginNotCalledErrorExceptionMessage); + } + + // Lock to access any abort commands and to make sure no renew tasks are executing during abort. + lock (_lockObject) + { + // First, stop any renew lease tasks. + DisposeRenewLeaseTimer(); + + // Execute any sqlite commands required to be executed on an abort and clear the collections. + ExecuteSqliteCommands(_abortCommandTexts, true); + + // Dispose of the current SQLite connection + DisposeCurrentConnection(); + } + } + + /// + /// If begin has already been called for this transaction. + /// Failed to begin the transaction. + /// If this instance is already disposed. + public void Begin() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(SqliteMessageQueueTransaction)); + } + + if (_sqliteConnection != null) + { + throw new InvalidOperationException("Begin has already been called"); + } + + // Create a new SQLite connection to the database (we create a new connection per transaction) + _sqliteConnection = new SqliteConnection(_sqliteConnectionString); + + try + { + _sqliteConnection.Open(); + } + catch (SqliteException e) + { + throw new MessageQueueTransactionBeginException("Failed to start transaction. Could not open connection to database.", e); + } + } + + /// + /// When this method is called before begin. + /// If this instance is already disposed. + public void Commit() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(SqliteMessageQueueTransaction)); + } + + if (_sqliteConnection == null) + { + throw new InvalidOperationException(BeginNotCalledErrorExceptionMessage); + } + + // Lock to access any commit commands and to make sure no renew tasks are executing during commit. + lock (_lockObject) + { + // First, stop any renew lease tasks. + DisposeRenewLeaseTimer(); + + // Execute any sqlite commands required to be executed on a commit and clear the collections. + ExecuteSqliteCommands(_commitCommandTexts, true); + + // Dispose of the current SQLite connection + DisposeCurrentConnection(); + } + } + + /// + public void Dispose() + { + Dispose(true); + } + + /// + /// Gets a SQLite command for this transaction. + /// + /// The command type. + /// The SQLite transaction for this command or null. + /// The SQLite command + /// When this method is called before begin. + internal SqliteCommand GetSqliteCommand(CommandType commandType = CommandType.Text, SqliteTransaction sqliteTransaction = null) + { + if (_sqliteConnection == null) + { + throw new InvalidOperationException(BeginNotCalledErrorExceptionMessage); + } + + return new SqliteCommand(string.Empty, _sqliteConnection) { CommandType = commandType, Transaction = sqliteTransaction }; + } + + /// + /// Enqueues a SQLite command to the commit queue. + /// + /// The SQL command text to run on a transaction commit. + internal void EnqueueCommitCommand(string sqliteCommandText) + { + lock (_lockObject) + { + _commitCommandTexts.Add(sqliteCommandText); + } + } + + /// + /// Enqueues a SQLite command to the abort queue. + /// + /// The SQL command text to run on a transaction abort. + internal void EnqueueAbortCommand(string sqliteCommandText) + { + lock (_lockObject) + { + _abortCommandTexts.Add(sqliteCommandText); + } + } + + /// + /// Enqueues a SQLite command to the abort queue. + /// + /// The SQL command text to run on a transaction abort. + /// When this method is called before begin. + internal void EnqueueRenewLeaseCommand(Func getSqliteCommandTextFunc) + { + // We cannot enqueue a renew lease task if the transaction has not started. + if (_sqliteConnection == null) + { + throw new InvalidOperationException(BeginNotCalledErrorExceptionMessage); + } + + lock (_lockObject) + { + _renewLeaseCommandTexts.Add(getSqliteCommandTextFunc); + + // Create a timer object that attempts to renew the lease. Only create if we haven't created one yet + // Note: We only create the timer when we enqueue a task as a performance optimisation. + if (_renewLeaseTimer == null) + { + _renewLeaseTimer = new Timer(ExecuteRenewLeaseCommandTexts, null, _transactionRenewLeaseMs, _transactionRenewLeaseMs); + } + } + } + + /// + /// Disposes of all managed resources. Also calls Abort() if the transaction is still open. + /// + /// If we are disposing. + protected virtual void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + // Try to abort the connection if we still have an open transaction + if (_sqliteConnection != null) + { + Abort(); + } + + // Clear any timer tasks + DisposeRenewLeaseTimer(); + + // Clear any open connection + DisposeCurrentConnection(); + + // Clear post transaction commands. + ClearPostTransactionCommands(); + } + + _disposed = true; + } + + /// + /// Disposes of the renew lease timer. + /// + private void DisposeRenewLeaseTimer() + { + if (_renewLeaseTimer != null) + { + _renewLeaseTimer.Dispose(); + _renewLeaseTimer = null; + } + } + + /// + /// Disposes of the current SQLite connection. + /// + private void DisposeCurrentConnection() + { + if (_sqliteConnection != null) + { + _sqliteConnection.Dispose(); + _sqliteConnection = null; + } + } + + /// + /// Clears of all abort/ commit/ renew lease commands + /// + private void ClearPostTransactionCommands() + { + lock (_lockObject) + { + _abortCommandTexts.Clear(); + _commitCommandTexts.Clear(); + _renewLeaseCommandTexts.Clear(); + } + } + + /// + /// Execute all the current SQLite commands in the renew tasks. + /// + /// The state information of the timer object. + private void ExecuteRenewLeaseCommandTexts(object timerStateInformation) + { + // Make sure we lock access for the time required to execute all renew commands. + // We want to make sure abort/ commit is not called whilst we are processing renew tasks; this could put the DB in an invalid state. + lock (_lockObject) + { + ExecuteSqliteCommands(_renewLeaseCommandTexts.Select(x => x()), clearCommandTexts: false); + } + } + + /// + /// Executes all the SQLite command texts as non query commands. + /// + /// The SQLite command texts to execute. + /// If we should clear all the command texts after the SQLite commands have been executed. + /// When this method is called before begin. + private void ExecuteSqliteCommands(IEnumerable commandTexts, bool clearCommandTexts) + { + if (_sqliteConnection == null) + { + throw new InvalidOperationException(BeginNotCalledErrorExceptionMessage); + } + + // Don't catch exceptions - we want to abort the transaction if any of these commands fail. + foreach (var commandText in commandTexts) + { + using (var sqliteCommand = GetSqliteCommand()) + { + var result = sqliteCommand.ExecuteNonQueryWithRetry(commandText); + } + } + + if (clearCommandTexts) + { + ClearPostTransactionCommands(); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/AssociationQueueItemBase.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/AssociationQueueItemBase.cs new file mode 100644 index 0000000..8a6eb2f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/AssociationQueueItemBase.cs @@ -0,0 +1,54 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + + using Newtonsoft.Json; + + /// + /// The Dicom association queue item. + /// + /// + [Serializable] + public class AssociationQueueItemBase : QueueItemBase + { + /// + /// Initializes a new instance of the class. + /// + /// The original association called application entity title. + /// The original association calling application entity title. + /// The association unique identifier. + /// The date time the association started. + /// The number of times this queue item has been dequeued. + [JsonConstructor] + public AssociationQueueItemBase( + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + Guid associationGuid, + DateTime associationDateTime, + int dequeueCount) + : base( + associationGuid: associationGuid, + associationDateTime: associationDateTime, + dequeueCount: dequeueCount) + { + CalledApplicationEntityTitle = calledApplicationEntityTitle; + CallingApplicationEntityTitle = callingApplicationEntityTitle; + } + + /// + /// Gets the original Dicom association called application entity title. + /// + /// + /// The original Dicom association called application entity title. + /// + public string CalledApplicationEntityTitle { get; } + + /// + /// Gets the original Dicom association calling application entity. + /// + /// + /// The original Dicom association calling application entity. + /// + public string CallingApplicationEntityTitle { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ConfigurationServiceConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ConfigurationServiceConfig.cs new file mode 100644 index 0000000..d06317d --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ConfigurationServiceConfig.cs @@ -0,0 +1,97 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + + /// + /// Model containing all configuration for starting the Gateway. + /// + public class ConfigurationServiceConfig : IEquatable + { + /// + /// Default configuration refresh delay, in seconds. + /// + public static readonly double DefaultConfigurationRefreshDelaySeconds = 60.0; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration creation date time. + /// The apply configuration date time. + /// Time between refreshing configuration. + public ConfigurationServiceConfig( + DateTime? configCreationDateTime = null, + DateTime? applyConfigDateTime = null, + double? configurationRefreshDelaySeconds = null) + { + ConfigCreationDateTime = configCreationDateTime ?? DateTime.UtcNow; + ApplyConfigDateTime = applyConfigDateTime ?? DateTime.UtcNow; + ConfigurationRefreshDelay = TimeSpan.FromSeconds(configurationRefreshDelaySeconds ?? DefaultConfigurationRefreshDelaySeconds); + } + + /// + /// Gets the configuration creation date time. + /// + /// + /// The configuration creation date time. + /// + public DateTime ConfigCreationDateTime { get; } + + /// + /// Gets the datetime when this config should be applied. + /// + /// + /// The datetime when this config should be applied + /// + public DateTime ApplyConfigDateTime { get; } + + /// + /// ConfigurationRefreshDelay in seconds. + /// + public double ConfigurationRefreshDelaySeconds => ConfigurationRefreshDelay.TotalSeconds; + + /// + /// The time between refreshing the current service configuration. + /// + [JsonIgnore] + public TimeSpan ConfigurationRefreshDelay { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ConfigurationServiceConfig); + } + + /// + public bool Equals(ConfigurationServiceConfig other) + { + return other != null && + ConfigCreationDateTime == other.ConfigCreationDateTime && + ApplyConfigDateTime == other.ApplyConfigDateTime && + ConfigurationRefreshDelay.Equals(other.ConfigurationRefreshDelay); + } + + /// + public override int GetHashCode() + { + var hashCode = -1437313058; + hashCode = hashCode * -1521134295 + ConfigCreationDateTime.GetHashCode(); + hashCode = hashCode * -1521134295 + ApplyConfigDateTime.GetHashCode(); + hashCode = hashCode * -1521134295 + ConfigurationRefreshDelay.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(ConfigurationServiceConfig left, ConfigurationServiceConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ConfigurationServiceConfig left, ConfigurationServiceConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DeleteQueueItem.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DeleteQueueItem.cs new file mode 100644 index 0000000..4408ec4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DeleteQueueItem.cs @@ -0,0 +1,97 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Newtonsoft.Json; + + /// + /// The delete queue item. + /// + /// + [Serializable] + public class DeleteQueueItem : AssociationQueueItemBase + { + /// + /// Prevents a default instance of the class from being created. + /// + private DeleteQueueItem() + : base( + calledApplicationEntityTitle: string.Empty, + callingApplicationEntityTitle: string.Empty, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + dequeueCount: 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The association queue item base. + /// The paths to delete (can either be a directory or file path). + public DeleteQueueItem( + AssociationQueueItemBase associationQueueItemBase, + params string[] paths) + : this( + associationQueueItemBase: associationQueueItemBase, + paths: paths.AsEnumerable()) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The association queue item base. + /// The paths to delete (can either be a directory or file path). + public DeleteQueueItem( + AssociationQueueItemBase associationQueueItemBase, + IEnumerable paths) + : this( + calledApplicationEntityTitle: associationQueueItemBase.CalledApplicationEntityTitle, + callingApplicationEntityTitle: associationQueueItemBase.CallingApplicationEntityTitle, + paths: paths, + associationGuid: associationQueueItemBase.AssociationGuid, + // We reset the date time to maximise the amount of time we try to delete (this association could have expried and we are trying to clean up). + associationDateTime: DateTime.UtcNow, + dequeueCount: 0) // Default to zero + { + } + + /// + /// Json constructor. + /// + /// The paths to delete (can either be a directory or file path). + /// The original association called application entity title. + /// The original association calling application entity title. + /// The association unique identifier. + /// The association date time. + /// The number of times this queue item has been dequeued. + [JsonConstructor] + public DeleteQueueItem( + IEnumerable paths, + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + Guid associationGuid, + DateTime associationDateTime, + int dequeueCount) + : base( + calledApplicationEntityTitle: calledApplicationEntityTitle, + callingApplicationEntityTitle: callingApplicationEntityTitle, + associationGuid: associationGuid, + associationDateTime: associationDateTime, + dequeueCount: dequeueCount) + { + Paths = paths ?? new string[0]; + } + + /// + /// Get the file paths. + /// + /// + /// The file paths. + /// + public IEnumerable Paths { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DequeueServiceConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DequeueServiceConfig.cs new file mode 100644 index 0000000..da643db --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DequeueServiceConfig.cs @@ -0,0 +1,121 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using Newtonsoft.Json; + + /// + /// The configuration for services based on dequeue message queues. + /// + public class DequeueServiceConfig : IEquatable + { + /// + /// The default value for the maxium time a queue message will be attempted to be reprocessed. + /// 7 days * 24 hours * 60 minutes * 60 seconds + /// + private const int DefaultMaximumQueueMessageAgeSeconds = 7 * 24 * 60 * 60; + + /// + /// The default value for the maxium time a dead letter will sit on the dead letter queue. + /// 30 minutes * 60 seconds + /// + private const int DefaultDeadLetterMoveFrequencySeconds = 30 * 60; + + /// + /// The maximum number of times we will dequeue an item before it is removed from the queue + /// and moved to the dead letter queue. + /// + public const int MaxDequeueCount = 1; + + /// + /// The time the service base will wait when a queue is empty to continue code execution. + /// If we want to stop the service in a reasonable time, we cannot block the main execution thread. + /// However, we also do not want to keep attempting reads from the queue in a tight loop. + /// + public static readonly TimeSpan DequeueTimeout = TimeSpan.FromSeconds(2); + + /// + /// The dead letter queue path format. + /// + public const string DeadLetterQueuePathFormat = "{0}DeadLetter"; + + /// + /// Create a queue path for the dead letter queue from a dequeue queue path. + /// + /// Dequeue queue path. + /// Dead letter queue path. + public static string DeadLetterQueuePath(string dequeueQueuePath) => + string.Format(CultureInfo.InvariantCulture, DeadLetterQueuePathFormat, dequeueQueuePath); + + /// + /// MaximumQueueMessageAge in seconds. + /// + public double MaximumQueueMessageAgeSeconds => MaximumQueueMessageAge.TotalSeconds; + + /// + /// The maximum age of a queue message. This is the longest amount of the time we will attempt to process the + /// message until it is removed from all queues (including dead letter queues). + [JsonIgnore] + public TimeSpan MaximumQueueMessageAge { get; } + + /// + /// DeadLetterMoveFrequency in seconds. + /// + public double DeadLetterMoveFrequencySeconds => DeadLetterMoveFrequency.TotalSeconds; + + /// + /// The frequency dead letter messages will be moved from the dead letter queue to the dequeue queue. + /// + [JsonIgnore] + public TimeSpan DeadLetterMoveFrequency { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The maximum age of a message in the queue before it is deleted and never re-processed in seconds. + /// The frequency a dead letter message should be moved back to its original queue and re-processed in seconds. + public DequeueServiceConfig( + double? maximumQueueMessageAgeSeconds, + double? deadLetterMoveFrequencySeconds) + { + MaximumQueueMessageAge = TimeSpan.FromSeconds(maximumQueueMessageAgeSeconds ?? DefaultMaximumQueueMessageAgeSeconds); + DeadLetterMoveFrequency = TimeSpan.FromSeconds(deadLetterMoveFrequencySeconds ?? DefaultDeadLetterMoveFrequencySeconds); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as DequeueServiceConfig); + } + + /// + public bool Equals(DequeueServiceConfig other) + { + return other != null && + MaximumQueueMessageAge.Equals(other.MaximumQueueMessageAge) && + DeadLetterMoveFrequency.Equals(other.DeadLetterMoveFrequency); + } + + /// + public override int GetHashCode() + { + var hashCode = 2144852902; + hashCode = hashCode * -1521134295 + MaximumQueueMessageAge.GetHashCode(); + hashCode = hashCode * -1521134295 + DeadLetterMoveFrequency.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(DequeueServiceConfig left, DequeueServiceConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(DequeueServiceConfig left, DequeueServiceConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DicomFileInformation.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DicomFileInformation.cs new file mode 100644 index 0000000..dde60d9 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DicomFileInformation.cs @@ -0,0 +1,151 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + + using Newtonsoft.Json; + + /// + /// The DICOM file information. + /// + [Serializable] + public class DicomFileInformation + { + /// + /// The pre-computed hash code of this instance. + /// + private readonly int _hashCode; + + /// + /// Initializes a new instance of the class. + /// + /// The patient identifier. + /// The study instance unique identifier. + /// The series instance unique identifier. + /// The modality of the Dicom object this file information represents. + [JsonConstructor] + public DicomFileInformation(string patientId, string studyInstanceUid, string seriesInstanceUid, string dicomModality) + { + PatientId = string.IsNullOrWhiteSpace(patientId) ? throw new ArgumentException(nameof(patientId)) : patientId; + StudyInstanceUid = string.IsNullOrWhiteSpace(studyInstanceUid) ? throw new ArgumentException(nameof(studyInstanceUid)) : studyInstanceUid; + SeriesInstanceUid = string.IsNullOrWhiteSpace(seriesInstanceUid) ? throw new ArgumentException(nameof(seriesInstanceUid)) : seriesInstanceUid; + DicomModality = string.IsNullOrWhiteSpace(dicomModality) ? throw new ArgumentException(nameof(dicomModality)) : dicomModality; + + _hashCode = $"{PatientId}-{StudyInstanceUid}-{SeriesInstanceUid}-{DicomModality}".GetHashCode(); + } + + /// + /// Gets the patient identifier. + /// + /// + /// The patient identifier. + /// + public string PatientId { get; } + + /// + /// Gets the study instance uid. + /// + /// + /// The study instance uid. + /// + public string StudyInstanceUid { get; } + + /// + /// Gets the series instance uid. + /// + /// + /// The series instance uid. + /// + public string SeriesInstanceUid { get; } + + /// + /// Gets the DICOM modality. + /// + /// + /// The DICOM modality. + /// + public string DicomModality { get; } + + /// + /// Equalses the specified DICOM file information. + /// + /// The DICOM file information. + /// If equal. + public bool Equals(DicomFileInformation dicomFileInformation) + { + if (dicomFileInformation is null) + { + return false; + } + + return + PatientId == dicomFileInformation.PatientId && + SeriesInstanceUid == dicomFileInformation.SeriesInstanceUid && + StudyInstanceUid == dicomFileInformation.StudyInstanceUid && + DicomModality == dicomFileInformation.DicomModality; + } + + /// + /// Implements the operator ==. + /// + /// The DICOM file information 1. + /// The DICOM file information 2. + /// + /// The result of the operator. + /// + public static bool operator ==(DicomFileInformation dicomFileInformation1, DicomFileInformation dicomFileInformation2) + { + if (dicomFileInformation1 is null) + { + return dicomFileInformation2 is null; + } + + return dicomFileInformation1.Equals(dicomFileInformation2); + } + + /// + /// Implements the operator !=. + /// + /// The DICOM file information 1. + /// The DICOM file information 2. + /// + /// The result of the operator. + /// + public static bool operator !=(DicomFileInformation dicomFileInformation1, DicomFileInformation dicomFileInformation2) + { + if (dicomFileInformation1 is null) + { + return !(dicomFileInformation2 is null); + } + + return !dicomFileInformation1.Equals(dicomFileInformation2); + } + + /// + /// Determines whether the specified , is equal to this instance. + /// + /// The to compare with this instance. + /// + /// true if the specified is equal to this instance; otherwise, false. + /// + public override bool Equals(object obj) + { + if (obj is DicomFileInformation dicomFileInformation) + { + return Equals(dicomFileInformation); + } + + return false; + } + + /// + /// Returns a hash code for this instance. + /// + /// + /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. + /// + public override int GetHashCode() + { + return _hashCode; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadQueueItem.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadQueueItem.cs new file mode 100644 index 0000000..fdb82a7 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadQueueItem.cs @@ -0,0 +1,171 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using Newtonsoft.Json; + + /// + /// Queue item used for downloading results from azure. + /// + [Serializable] + public class DownloadQueueItem : AssociationQueueItemBase + { + /// + /// Prevents a default instance of the class from being created. + /// + private DownloadQueueItem() + : base( + calledApplicationEntityTitle: string.Empty, + callingApplicationEntityTitle: string.Empty, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + dequeueCount: 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The segmentation unique identifier. + /// The model identifier.The directory to store all results. + /// The reference dicom files. + /// The original association called application entity title. + /// The original association calling application entity title. + /// The destination application entity (can be null, and will be refreshed in the push service). + /// The tag replacements as a Json string. + /// The association unique identifier. + /// The association date time. + /// If this is a dry run download, no push to destination. + public DownloadQueueItem( + string segmentationId, + string modelId, + string resultsDirectory, + IEnumerable referenceDicomFiles, + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + GatewayApplicationEntity destinationApplicationEntity, + string tagReplacementJsonString, + Guid associationGuid, + DateTime associationDateTime, + bool isDryRun) + : this( + segmentationId: segmentationId, + modelId: modelId, + resultsDirectory: resultsDirectory, + referenceDicomFiles: referenceDicomFiles, + calledApplicationEntityTitle: calledApplicationEntityTitle, + callingApplicationEntityTitle: callingApplicationEntityTitle, + destinationApplicationEntity: destinationApplicationEntity, + tagReplacementJsonString: tagReplacementJsonString, + associationGuid: associationGuid, + associationDateTime: associationDateTime, + dequeueCount: 0, + isDryRun: isDryRun) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The segmentation unique identifier. + /// The model identifier.The directory to store all results. + /// The reference dicom files. + /// The original association called application entity title. + /// The original association calling application entity title. + /// The destination application entity (can be null, and will be refreshed in the push service). + /// The tag replacements as a Json string. + /// The association unique identifier. + /// The association date time. + /// The number of times this queue item has been dequeued. + /// If this is a dry run download, no push to destination. + [JsonConstructor] + public DownloadQueueItem( + string segmentationId, + string modelId, + string resultsDirectory, + IEnumerable referenceDicomFiles, + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + GatewayApplicationEntity destinationApplicationEntity, + string tagReplacementJsonString, + Guid associationGuid, + DateTime associationDateTime, + int dequeueCount, + bool isDryRun) + : base( + calledApplicationEntityTitle, + callingApplicationEntityTitle, + associationGuid, + associationDateTime, + dequeueCount) + { + SegmentationID = segmentationId; + ModelId = modelId; + ResultsDirectory = !string.IsNullOrWhiteSpace(resultsDirectory) ? resultsDirectory : throw new ArgumentException(nameof(ResultsDirectory)); + ReferenceDicomFiles = referenceDicomFiles?.ToArray() ?? throw new ArgumentNullException(nameof(referenceDicomFiles)); + DestinationApplicationEntity = destinationApplicationEntity; + TagReplacementJsonString = tagReplacementJsonString; + IsDryRun = isDryRun; + } + + /// + /// Gets the download identifier for getting the result from the service. + /// + /// + /// The download identifier. + /// + public string SegmentationID { get; } + + /// + /// Gets the model identifier. + /// + /// + /// The model identifier. + /// + public string ModelId { get; } + + /// + /// Gets the dicom file to be used for de-anonymization. + /// + /// + /// The dicom file. + /// + public IEnumerable ReferenceDicomFiles { get; } + + /// + /// Gets the destination application entity title. + /// + /// + /// The destination application entity title. + /// + public GatewayApplicationEntity DestinationApplicationEntity { get; } + + /// + /// Gets the tag replacement json string. + /// + /// + /// The tag replacement json string. + /// + public string TagReplacementJsonString { get; } + + /// + /// Gets a value indicating whether this instance is dry run. + /// + /// + /// true if this instance is dry run; otherwise, false. + /// + public bool IsDryRun { get; } + + /// + /// Gets the directory to store all results. + /// + /// + /// The results directory. + /// + public string ResultsDirectory { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadServiceConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadServiceConfig.cs new file mode 100644 index 0000000..12badd5 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/DownloadServiceConfig.cs @@ -0,0 +1,97 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + using Newtonsoft.Json; + + /// + /// Additional configuration options for the DownloadService. + /// + public class DownloadServiceConfig : IEquatable + { + /// + /// Default for + /// + private readonly TimeSpan DefaultDownloadRetryTimespan = TimeSpan.FromSeconds(5); + + /// + /// Default for + /// + private static TimeSpan DefaultDownloadWaitTimeout = TimeSpan.FromHours(1); + + /// + /// DownloadRetryTimespan in seconds. + /// + public double DownloadRetryTimespanInSeconds => DownloadRetryTimespan.TotalSeconds; + + /// + /// The time we will wait between calling the API for a segmentation status. + /// + [JsonIgnore] + public TimeSpan DownloadRetryTimespan { get; } + + /// + /// DownloadWaitTimeout in seconds. + /// + public double DownloadWaitTimeoutInSeconds => DownloadWaitTimeout.TotalSeconds; + + /// + /// The maximum time the service will wait for a result to finish. + /// + [JsonIgnore] + public TimeSpan DownloadWaitTimeout { get; } + + /// + /// Initialize a new instance of the class. + /// + /// Download retry timespan in seconds. + /// Download wait timeout in seconds. + public DownloadServiceConfig( + double? downloadRetryTimespanInSeconds = null, + double? downloadWaitTimeoutInSeconds = null) + { + DownloadRetryTimespan = downloadRetryTimespanInSeconds.HasValue ? + TimeSpan.FromSeconds(downloadRetryTimespanInSeconds.Value) : DefaultDownloadRetryTimespan; + + DownloadWaitTimeout = downloadWaitTimeoutInSeconds.HasValue ? + TimeSpan.FromSeconds(downloadWaitTimeoutInSeconds.Value) : DefaultDownloadWaitTimeout; + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as DownloadServiceConfig); + } + + /// + public bool Equals(DownloadServiceConfig other) + { + return other != null && + DefaultDownloadRetryTimespan.Equals(other.DefaultDownloadRetryTimespan) && + DownloadRetryTimespan.Equals(other.DownloadRetryTimespan) && + DownloadWaitTimeout.Equals(other.DownloadWaitTimeout); + } + + /// + public override int GetHashCode() + { + var hashCode = -37651564; + hashCode = hashCode * -1521134295 + DefaultDownloadRetryTimespan.GetHashCode(); + hashCode = hashCode * -1521134295 + DownloadRetryTimespan.GetHashCode(); + hashCode = hashCode * -1521134295 + DownloadWaitTimeout.GetHashCode(); + return hashCode; + } + + /// + public static bool operator ==(DownloadServiceConfig left, DownloadServiceConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(DownloadServiceConfig left, DownloadServiceConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayApplicationEntity.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayApplicationEntity.cs new file mode 100644 index 0000000..b7e60f5 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayApplicationEntity.cs @@ -0,0 +1,42 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + + using Newtonsoft.Json; + + /// + /// Gateway representation of an application entity. + /// + [Serializable] + public class GatewayApplicationEntity + { + /// + /// Constructor. + /// + /// The application entity title. + /// The application entity port number. + /// The application entity IP address. + [JsonConstructor] + public GatewayApplicationEntity(string title, int port, string ipAddress) + { + Title = title; + Port = port; + IpAddress = ipAddress; + } + + /// + /// Gets the application entity service title. + /// + public string Title { get; } + + /// + /// Gets the port number for the application entity. + /// + public int Port { get; } + + /// + /// Gets the IP address of the application entity. + /// + public string IpAddress { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayProcessorConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayProcessorConfig.cs new file mode 100644 index 0000000..df7d241 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayProcessorConfig.cs @@ -0,0 +1,99 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + + /// + /// Configuration class for the Processor service. + /// + public class GatewayProcessorConfig : IEquatable + { + /// + /// Configuration for the Processor service itself. + /// + public ServiceSettings ServiceSettings { get; } + + /// + /// Configuration for the inference API. + /// + public ProcessorSettings ProcessorSettings { get; } + + /// + /// Configuration for the services, all are based on the class. + /// + public DequeueServiceConfig DequeueServiceConfig { get; } + + /// + /// Configuration for the download service. + /// + public DownloadServiceConfig DownloadServiceConfig { get; } + + /// + /// Configuration for the configuration service. + /// + public ConfigurationServiceConfig ConfigurationServiceConfig { get; } + + /// + /// Initializes a new instance of the class. + /// + /// Service settings. + /// Processor settings. + /// Dequeue service config. + /// Download service config. + /// Configuration service config. + public GatewayProcessorConfig( + ServiceSettings serviceSettings, + ProcessorSettings processorSettings, + DequeueServiceConfig dequeueServiceConfig, + DownloadServiceConfig downloadServiceConfig, + ConfigurationServiceConfig configurationServiceConfig) + { + ServiceSettings = serviceSettings; + ProcessorSettings = processorSettings; + DequeueServiceConfig = dequeueServiceConfig; + DownloadServiceConfig = downloadServiceConfig; + ConfigurationServiceConfig = configurationServiceConfig; + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as GatewayProcessorConfig); + } + + /// + public bool Equals(GatewayProcessorConfig other) + { + return other != null && + EqualityComparer.Default.Equals(ServiceSettings, other.ServiceSettings) && + EqualityComparer.Default.Equals(ProcessorSettings, other.ProcessorSettings) && + EqualityComparer.Default.Equals(DequeueServiceConfig, other.DequeueServiceConfig) && + EqualityComparer.Default.Equals(DownloadServiceConfig, other.DownloadServiceConfig) && + EqualityComparer.Default.Equals(ConfigurationServiceConfig, other.ConfigurationServiceConfig); + } + + /// + public override int GetHashCode() + { + var hashCode = -1353736591; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ServiceSettings); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ProcessorSettings); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DequeueServiceConfig); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DownloadServiceConfig); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ConfigurationServiceConfig); + return hashCode; + } + + /// + public static bool operator ==(GatewayProcessorConfig left, GatewayProcessorConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(GatewayProcessorConfig left, GatewayProcessorConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayReceiveConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayReceiveConfig.cs new file mode 100644 index 0000000..1159fe1 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/GatewayReceiveConfig.cs @@ -0,0 +1,79 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + + /// + /// Configuration class for the Receiver service. + /// + public class GatewayReceiveConfig : IEquatable + { + /// + /// Configuration for the Receiver service itself. + /// + public ServiceSettings ServiceSettings { get; } + + /// + /// Configuration for the Receive subservice. + /// + public ReceiveServiceConfig ReceiveServiceConfig { get; } + + /// + /// Configuration for the configuration service. + /// + public ConfigurationServiceConfig ConfigurationServiceConfig { get; } + + /// + /// Initialize a new instance of the class. + /// + /// Service settings. + /// Receive service config. + /// Configuration service config. + public GatewayReceiveConfig( + ServiceSettings serviceSettings, + ReceiveServiceConfig receiveServiceConfig, + ConfigurationServiceConfig configurationServiceConfig) + { + ServiceSettings = serviceSettings; + ReceiveServiceConfig = receiveServiceConfig; + ConfigurationServiceConfig = configurationServiceConfig; + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as GatewayReceiveConfig); + } + + /// + public bool Equals(GatewayReceiveConfig other) + { + return other != null && + EqualityComparer.Default.Equals(ServiceSettings, other.ServiceSettings) && + EqualityComparer.Default.Equals(ReceiveServiceConfig, other.ReceiveServiceConfig) && + EqualityComparer.Default.Equals(ConfigurationServiceConfig, other.ConfigurationServiceConfig); + } + + /// + public override int GetHashCode() + { + var hashCode = -1539593518; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ServiceSettings); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ReceiveServiceConfig); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ConfigurationServiceConfig); + return hashCode; + } + + /// + public static bool operator ==(GatewayReceiveConfig left, GatewayReceiveConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(GatewayReceiveConfig left, GatewayReceiveConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/Microsoft.InnerEye.Gateway.Models.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/Microsoft.InnerEye.Gateway.Models.csproj new file mode 100644 index 0000000..18811c1 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/Microsoft.InnerEye.Gateway.Models.csproj @@ -0,0 +1,31 @@ + + + net462 + x64 + Microsoft.InnerEye.Gateway.Models + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Models for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ProcessorSettings.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ProcessorSettings.cs new file mode 100644 index 0000000..4dc3ec4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ProcessorSettings.cs @@ -0,0 +1,69 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + + /// + /// ProcessorSettings class + /// + public class ProcessorSettings : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// License key environment variable. + /// Inference API Uri. + public ProcessorSettings( + string licenseKeyEnvVar, + Uri inferenceUri) + { + LicenseKeyEnvVar = licenseKeyEnvVar; + InferenceUri = inferenceUri; + } + + /// + /// Gets the license key environment variable. + /// + public string LicenseKeyEnvVar { get; } + + /// + /// Gets the inference API Uri. + /// + public Uri InferenceUri { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ProcessorSettings); + } + + /// + public bool Equals(ProcessorSettings other) + { + return other != null && + LicenseKeyEnvVar == other.LicenseKeyEnvVar && + EqualityComparer.Default.Equals(InferenceUri, other.InferenceUri); + } + + /// + public override int GetHashCode() + { + var hashCode = 1943766103; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(LicenseKeyEnvVar); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(InferenceUri); + return hashCode; + } + + /// + public static bool operator ==(ProcessorSettings left, ProcessorSettings right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ProcessorSettings left, ProcessorSettings right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/PushQueueItem.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/PushQueueItem.cs new file mode 100644 index 0000000..854113d --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/PushQueueItem.cs @@ -0,0 +1,101 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + + using Newtonsoft.Json; + + /// + /// The push queue item. + /// + /// + [Serializable] + public class PushQueueItem : AssociationQueueItemBase + { + /// + /// Prevents a default instance of the class from being created. + /// + private PushQueueItem() + : base( + calledApplicationEntityTitle: string.Empty, + callingApplicationEntityTitle: string.Empty, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + dequeueCount: 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The destination application entity. + /// The original association called application entity title. + /// The original association calling application entity title. + /// The association unique identifier. + /// The association date time. + /// The collection of file paths that must be sent in the push. + public PushQueueItem( + GatewayApplicationEntity destinationApplicationEntity, + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + Guid associationGuid, + DateTime associationDateTime, + params string[] filePaths) + : this( + destinationApplicationEntity: destinationApplicationEntity, + calledApplicationEntityTitle: calledApplicationEntityTitle, + callingApplicationEntityTitle: callingApplicationEntityTitle, + associationGuid: associationGuid, + associationDateTime: associationDateTime, + dequeueCount: 0, + filePaths: filePaths) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The destination application entity. + /// The original association called application entity title. + /// The original association calling application entity title. + /// The association unique identifier. + /// The association date time. + /// The number of times this item has been dequeued. + /// The collection of file paths that must be sent in the push. + [JsonConstructor] + public PushQueueItem( + GatewayApplicationEntity destinationApplicationEntity, + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + Guid associationGuid, + DateTime associationDateTime, + int dequeueCount, + params string[] filePaths) + : base( + calledApplicationEntityTitle, + callingApplicationEntityTitle, + associationGuid, + associationDateTime, + dequeueCount) + { + DestinationApplicationEntity = destinationApplicationEntity; + FilePaths = filePaths ?? new string[0]; + } + + /// + /// Gets or sets the destination application entity title. + /// + /// + /// The destination application entity title. + /// + public GatewayApplicationEntity DestinationApplicationEntity { get; set; } + + /// + /// Gets the collection of file paths that must be sent in the push. + /// + /// + /// The collection of file paths that must be sent in the push. + /// + public IEnumerable FilePaths { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/QueueItemBase.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/QueueItemBase.cs new file mode 100644 index 0000000..4e400ea --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/QueueItemBase.cs @@ -0,0 +1,51 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + + using Newtonsoft.Json; + + /// + /// The queue item base. + /// + [Serializable] + public class QueueItemBase + { + /// + /// Initializes a new instance of the class. + /// + /// The association unique identifier. + /// The date time the association started. + /// The number of times this queue item has been dequeued. + [JsonConstructor] + public QueueItemBase(Guid associationGuid, DateTime associationDateTime, int dequeueCount) + { + AssociationGuid = associationGuid; + AssociationDateTime = associationDateTime; + DequeueCount = dequeueCount; + } + + /// + /// Gets the association unique identifier. + /// + /// + /// The association unique identifier. + /// + public Guid AssociationGuid { get; } + + /// + /// Gets the date time when the Dicom association started. + /// + /// + /// Gets the date time when the Dicom association started. + /// + public DateTime AssociationDateTime { get; } + + /// + /// Gets or sets the dequeue count. + /// + /// + /// The dequeue count for this item. + /// + public int DequeueCount { get; set; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ReceiveServiceConfig.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ReceiveServiceConfig.cs new file mode 100644 index 0000000..020c2a2 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ReceiveServiceConfig.cs @@ -0,0 +1,140 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Dicom; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Newtonsoft.Json; + + /// + /// Model containing all configuration for the receive service. + /// + public class ReceiveServiceConfig : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// The gateway dicom end point. + /// The root dicom folder. + /// The accepted sop classes and transfer syntaxes UI ds. + /// + /// gatewayDicomEndPoint + /// or + /// rootDicomFolder + /// or + /// acceptedSopClassesAndTransferSyntaxesUIDs + /// + public ReceiveServiceConfig( + DicomEndPoint gatewayDicomEndPoint, + string rootDicomFolder, + Dictionary acceptedSopClassesAndTransferSyntaxesUIDs) + { + GatewayDicomEndPoint = gatewayDicomEndPoint ?? throw new ArgumentNullException(nameof(gatewayDicomEndPoint)); + RootDicomFolder = rootDicomFolder ?? throw new ArgumentNullException(nameof(rootDicomFolder)); + AcceptedSopClassesAndTransferSyntaxesUIDs = acceptedSopClassesAndTransferSyntaxesUIDs ?? throw new ArgumentNullException(nameof(acceptedSopClassesAndTransferSyntaxesUIDs)); + + if (AcceptedSopClassesAndTransferSyntaxesUIDs.Count == 0) + { + throw new ArgumentException("The GatewayReceiveConfiguration must have at least 1 acceptable SOPClassUID", nameof(acceptedSopClassesAndTransferSyntaxesUIDs)); + } + + if (AcceptedSopClassesAndTransferSyntaxesUIDs.Any(kvp => kvp.Value.Length == 0)) + { + throw new ArgumentException("Every SopClassUID must have at least 1 supported Transfer Syntax", nameof(acceptedSopClassesAndTransferSyntaxesUIDs)); + } + } + + /// + /// Clone this into a new instance of the class. + /// + /// The gateway dicom end point. + /// The root dicom folder. + /// The accepted sop classes and transfer syntaxes UI ds. + public ReceiveServiceConfig With( + DicomEndPoint gatewayDicomEndPoint = null, + string rootDicomFolder = null, + Dictionary acceptedSopClassesAndTransferSyntaxesUIDs = null) => + new ReceiveServiceConfig( + gatewayDicomEndPoint ?? GatewayDicomEndPoint, + rootDicomFolder ?? RootDicomFolder, + acceptedSopClassesAndTransferSyntaxesUIDs ?? AcceptedSopClassesAndTransferSyntaxesUIDs); + + /// + /// Gets GatewayDicomEndPoint + /// + public DicomEndPoint GatewayDicomEndPoint { get; } + + /// + /// Gets the root Dicom folder where we store data. + /// + public string RootDicomFolder { get; } + + /// + /// Gets the accepted Sop classes and transfer syntaxes. + /// + [JsonIgnore] + public Dictionary AcceptedSopClassesAndTransferSyntaxes { + get { + return AcceptedSopClassesAndTransferSyntaxesUIDs + .ToDictionary( + keyValue => DicomUID.Parse(keyValue.Key), + keyValue => keyValue.Value.Select(x => DicomTransferSyntax.Parse(x)).ToArray()); + } + } + + /// + /// Gets the accepted Sop classes and transfer syntaxes. + /// + public Dictionary AcceptedSopClassesAndTransferSyntaxesUIDs { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ReceiveServiceConfig); + } + + /// + public bool Equals(ReceiveServiceConfig other) + { + return other != null && + EqualityComparer.Default.Equals(GatewayDicomEndPoint, other.GatewayDicomEndPoint) && + RootDicomFolder == other.RootDicomFolder && + CompareAcceptedSopClassesAndTransferSyntaxesUIDs(AcceptedSopClassesAndTransferSyntaxesUIDs, other.AcceptedSopClassesAndTransferSyntaxesUIDs); + } + + /// + /// Compare the acceptedSopClassesAndTransferSyntaxesUIDs. + /// + /// Left object to compare. + /// Right object to compare. + /// True if equal, false otherwise. + private static bool CompareAcceptedSopClassesAndTransferSyntaxesUIDs(Dictionary left, Dictionary right) + { + return left.Count == right.Count && + left.Keys.All(key => right.ContainsKey(key) && left[key].SequenceEqual(right[key])); + } + + /// + public override int GetHashCode() + { + var hashCode = 588952872; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(GatewayDicomEndPoint); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(RootDicomFolder); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(AcceptedSopClassesAndTransferSyntaxesUIDs); + return hashCode; + } + + /// + public static bool operator ==(ReceiveServiceConfig left, ReceiveServiceConfig right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ReceiveServiceConfig left, ReceiveServiceConfig right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ServiceSettings.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ServiceSettings.cs new file mode 100644 index 0000000..7a46dda --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/ServiceSettings.cs @@ -0,0 +1,60 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + using System.Collections.Generic; + + /// + /// Service settings class + /// + public class ServiceSettings : IEquatable + { + /// + /// Initializes a new instance of the class. + /// + /// If we should run the services as a console application. + public ServiceSettings( + bool runAsConsole) + { + RunAsConsole = runAsConsole; + } + + /// + /// Gets if we should run the Windows Services as a console application. + /// + /// + /// If we should run the Windows Services as a console application. + /// + public bool RunAsConsole { get; } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ServiceSettings); + } + + /// + public bool Equals(ServiceSettings other) + { + return other != null && + RunAsConsole == other.RunAsConsole; + } + + /// + public override int GetHashCode() + { + return 450039479 + RunAsConsole.GetHashCode(); + } + + /// + public static bool operator ==(ServiceSettings left, ServiceSettings right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(ServiceSettings left, ServiceSettings right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/UploadQueueItem.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/UploadQueueItem.cs new file mode 100644 index 0000000..d29a1d6 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Models/UploadQueueItem.cs @@ -0,0 +1,103 @@ +namespace Microsoft.InnerEye.Gateway.Models +{ + using System; + + using Newtonsoft.Json; + + /// + /// The queue item used for adding information onto the queue about files received over Dicom. + /// + [Serializable] + public class UploadQueueItem : AssociationQueueItemBase + { + /// + /// Prevents a default instance of the class from being created. + /// + private UploadQueueItem() + : base( + calledApplicationEntityTitle: string.Empty, + callingApplicationEntityTitle: string.Empty, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + dequeueCount: 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The original association called application entity title. + /// The original association calling application entity title. + /// The folder path where the Dicom Data was stored. + /// The root DICOM folder path. + /// The remote implementation version. + /// The remote implementation class uid. + /// The association unique identifier. + /// The association date time. + public UploadQueueItem( + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + string associationFolderPath, + string rootDicomFolderPath, + Guid associationGuid, + DateTime associationDateTime) + : this( + calledApplicationEntityTitle: calledApplicationEntityTitle, + callingApplicationEntityTitle: callingApplicationEntityTitle, + associationFolderPath: associationFolderPath, + rootDicomFolderPath: rootDicomFolderPath, + associationGuid: associationGuid, + associationDateTime: associationDateTime, + dequeueCount: 0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The original association called application entity title. + /// The original association calling application entity title. + /// The folder path where the Dicom Data was stored. + /// The root DICOM folder path. + /// The remote implementation version. + /// The remote implementation class uid. + /// The association unique identifier. + /// The association date time. + /// The number of times this queue item has been dequeued. + [JsonConstructor] + public UploadQueueItem( + string calledApplicationEntityTitle, + string callingApplicationEntityTitle, + string associationFolderPath, + string rootDicomFolderPath, + Guid associationGuid, + DateTime associationDateTime, + int dequeueCount) + : base( + calledApplicationEntityTitle, + callingApplicationEntityTitle, + associationGuid, + associationDateTime, + dequeueCount) + { + AssociationFolderPath = !string.IsNullOrWhiteSpace(associationFolderPath) ? associationFolderPath : throw new ArgumentException(nameof(associationFolderPath)); + RootDicomFolderPath = !string.IsNullOrWhiteSpace(rootDicomFolderPath) ? rootDicomFolderPath : throw new ArgumentException(nameof(rootDicomFolderPath)); + } + + /// + /// Gets the folder path where the association data was written to. + /// + /// + /// The folder path where the association data was written to. + /// + public string AssociationFolderPath { get; } + + /// + /// Gets the root dicom folder path. + /// + /// + /// The root dicom folder path. + /// + public string RootDicomFolderPath { get; } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteReadException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteReadException.cs new file mode 100644 index 0000000..f2ea20b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteReadException.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.Gateway.Sqlite.Exceptions +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception class for any exceptions during a read from a message queue. + /// + [Serializable] + public class SqliteReadException : Exception + { + /// + /// Prevents a default instance of the class from being created. + /// + private SqliteReadException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public SqliteReadException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public SqliteReadException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected SqliteReadException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteWriteException.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteWriteException.cs new file mode 100644 index 0000000..e709839 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Exceptions/SqliteWriteException.cs @@ -0,0 +1,48 @@ +namespace Microsoft.InnerEye.Gateway.Sqlite.Exceptions +{ + using System; + using System.Runtime.Serialization; + + /// + /// Exception class for any exceptions during a write to a Sqlite. + /// + [Serializable] + public class SqliteWriteException : Exception + { + /// + /// Prevents a default instance of the class from being created. + /// + private SqliteWriteException() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The message that describes the error. + public SqliteWriteException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception, or a null reference (Nothing in Visual Basic) if no inner exception is specified. + public SqliteWriteException(string message, Exception innerException) + : base(message, innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected SqliteWriteException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Extensions/SqliteExtensions.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Extensions/SqliteExtensions.cs new file mode 100644 index 0000000..0fbbaf0 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Extensions/SqliteExtensions.cs @@ -0,0 +1,127 @@ +namespace Microsoft.InnerEye.Gateway.Sqlite.Extensions +{ + using System; + using System.Diagnostics.CodeAnalysis; + + using Microsoft.Data.Sqlite; + using Microsoft.InnerEye.Gateway.Sqlite.Exceptions; + using Newtonsoft.Json; + + /// + /// Extension methods for SQLite. + /// + public static class SqliteExtensions + { + /// + /// Exception message format for null or whitespace parameters. + /// + private const string NullOrWhitespaceParameterExceptionMessageFormat = "{0} is null or whitespace."; + + /// + /// Executes a non-transactional non-query against the specified database on a new database connection. + /// + /// The string to use to connect to the database. + /// The non-query SQLite command text to issue. + /// The number of rows modified by the non-query. + /// The connection string or command text is null or whitespace. + /// If we fail to open a connection to the database. + [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")] + public static int ExecuteNonQueryNewConnection(string connectionString, string commandText) + { + connectionString = string.IsNullOrWhiteSpace(connectionString) ? throw new ArgumentException(string.Format(NullOrWhitespaceParameterExceptionMessageFormat, nameof(connectionString)), nameof(connectionString)) : connectionString; + commandText = string.IsNullOrWhiteSpace(commandText) ? throw new ArgumentException(string.Format(NullOrWhitespaceParameterExceptionMessageFormat, nameof(commandText)), nameof(commandText)) : commandText; + + using (var connection = new SqliteConnection(connectionString)) + { + connection.Open(); + + using (var command = new SqliteCommand(commandText, connection)) + { + // Use the extension method to execute the non query (this handles retry logic) + return command.ExecuteNonQueryWithRetry(commandText); + } + } + } + + /// + /// Executes a non query using the command and command text. + /// + /// The command to execute the non query. + /// The command text to issue. + /// The number of times the method will attempt to execute the non-query on SQLite failures. + /// The number of rows modified by the non-query. + /// If the command text is null or white space. + /// If the SQLite command is null. + /// Failed to execute the non query. + [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")] + public static int ExecuteNonQueryWithRetry(this SqliteCommand command, string commandText, int retryCount = 3) + { + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } + + command.CommandText = string.IsNullOrWhiteSpace(commandText) ? throw new ArgumentException(string.Format(NullOrWhitespaceParameterExceptionMessageFormat, nameof(commandText)), nameof(commandText)) : commandText; + + try + { + return command.ExecuteNonQuery(); + } + // Only catch SQLite exceptions + catch (SqliteException e) + { + if (retryCount == 0) + { + throw new SqliteWriteException($"[SQLite ExecuteNonQuery] Failed to execute non query: {commandText}. Exception: {e.Message}", e); + } + } + + // Retry if we have the ability to try again. + return ExecuteNonQueryWithRetry(command, commandText, retryCount - 1); + } + + /// + /// Executes a scalar against the SQLite command and de-serializes the result to type T. + /// + /// The type to de-serialize the result to. + /// The SQLite command to execute the non-scalar against. + /// The command text to run. + /// The number of times the method will attempt to execute the scalar on SQLite failures. + /// The de-serializes first row/ first column from the command. + /// If the command text is null or white space. + /// If the SQLite command is null. + /// If we fail to read anything from the first row/ column. + [SuppressMessage("Microsoft.Security", "CA2100:Review SQL queries for security vulnerabilities")] + public static T ExecuteScalarWithRetry(this SqliteCommand command, string commandText, int retryCount = 3) + { + if (command == null) + { + throw new ArgumentNullException(nameof(command)); + } + + command.CommandText = string.IsNullOrWhiteSpace(commandText) ? throw new ArgumentException(string.Format(NullOrWhitespaceParameterExceptionMessageFormat, nameof(commandText)), nameof(commandText)) : commandText; + + try + { + var result = command.ExecuteScalar(); + + if (result == null) + { + throw new SqliteReadException($"Failed to execute scalar command: {commandText}."); + } + + return JsonConvert.DeserializeObject(Convert.ToString(result)); + } + // Only catch SQLite exceptions + catch (SqliteException e) + { + if (retryCount == 0) + { + throw new SqliteReadException($"[SQLite ExecuteNonQuery] Failed to execute scalar command: {commandText}. Exception: {e.Message}", e); + } + } + + return ExecuteScalarWithRetry(command, commandText, retryCount - 1); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Microsoft.InnerEye.Gateway.Sqlite.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Microsoft.InnerEye.Gateway.Sqlite.csproj new file mode 100644 index 0000000..03c4b47 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/Microsoft.InnerEye.Gateway.Sqlite.csproj @@ -0,0 +1,27 @@ + + + net462 + x64 + Microsoft.InnerEye.Gateway.Sqlite + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Common Sqlite framework for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/SqliteManager.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/SqliteManager.cs new file mode 100644 index 0000000..c2d5070 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Gateway.Sqlite/SqliteManager.cs @@ -0,0 +1,102 @@ +namespace Microsoft.InnerEye.Gateway.Sqlite +{ + using System; + using System.IO; + using System.Linq; + + using SQLitePCL; + + using Microsoft.Data.Sqlite; + using Microsoft.InnerEye.Gateway.Sqlite.Extensions; + + public class SqliteManager + { + /// + /// The database connection path. + /// Note: To explore the database file you can use the tool: https://sqlitebrowser.org/ + /// + private readonly string DatabaseConnectionStringFormat; + + /// + /// The connection string to the database. + /// + public readonly string DatabaseConnectionString; + + /// + /// The table name. + /// + public readonly string _tableName; + + /// + /// Exception message format for null or whitespace parameters. + /// + protected const string NullOrWhitespaceParameterExceptionMessageFormat = "{0} is null or whitespace."; + + /// + /// The SQLite command text format for clearing a table of all rows. + /// + public const string ClearTableCommandTextFormat = "DELETE FROM [{0}]"; + + /// + /// The queue table data column names and data type. + /// + private readonly (string ColumnName, string ColumnDataType)[] Columns; + + public SqliteManager(string tableName, string databaseConnectionStringFormat, (string ColumnName, string ColumnDataType)[] columns) + { + _tableName = !string.IsNullOrWhiteSpace(tableName) ? tableName : throw new ArgumentException(nameof(tableName)); + + DatabaseConnectionStringFormat = databaseConnectionStringFormat; + Columns = columns; + // Get the database connection string (create the local AppData folder if it does not exist) + DatabaseConnectionString = GetDatabaseConnectionString(); + + // Set the SQLite engine provider (we use SQLite3) + Batteries_V2.Init(); + //raw.SetProvider(new SQLite3Provider_e_sqlite3()); + + CreateTableIfNotExists(); + } + + /// + /// The SQLite command text format for inserting a row into a table. + /// + public string GetInsertRowCommandFormat() + { + return $"INSERT INTO [{_tableName}] ({string.Join(", ", Columns.Select(x => x.ColumnName))}) VALUES({string.Join(", ", Columns.Select(x => $"@{x.ColumnName}"))})"; + } + + /// + /// Creates the queue table if it does not exist. + /// + /// The SQLite command the method will use to create the table if it does not exist. + /// The connection string or command text is null or whitespace. + /// If we fail to open a connection to the database. + public void CreateTableIfNotExists() + { + string createTableIfNotExistsCommandFormat = "CREATE TABLE IF NOT EXISTS [{0}] " + $"({string.Join(", ", Columns.Select(x => $"{x.ColumnName} {x.ColumnDataType}"))})"; + + SqliteExtensions.ExecuteNonQueryNewConnection( + connectionString: DatabaseConnectionString, + commandText: string.Format(createTableIfNotExistsCommandFormat, _tableName)); + } + + /// + /// Gets the connection string the database. + /// The database is stored in the Microsoft InnerEye Gateway Log local application data folder. + /// This folder will be created in this method if it does not exist. + /// + /// The database connection string. + private string GetDatabaseConnectionString() + { + var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\Microsoft InnerEye Gateway\"); + + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path); + } + + return string.Format(DatabaseConnectionStringFormat, path); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/AETConfigModel.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/AETConfigModel.cs new file mode 100644 index 0000000..8fb246e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/AETConfigModel.cs @@ -0,0 +1,93 @@ +namespace Microsoft.InnerEye.Listener.Common +{ + using System; + using System.Collections.Generic; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + /// + /// Custom Model Class to get the values of ClientAETConfig based on the + /// values of CalledAET and Calling AET, from the json configuration + /// + public class AETConfigModel : IEquatable + { + /// + /// Gets the Called Application Entity Title + /// + public string CalledAET { get; } + + /// + /// Gets the Calling Application Entity Title + /// + public string CallingAET { get; } + + /// + /// Encodes how an AET is configured + /// + public ClientAETConfig AETConfig { get; } + + /// + /// Initialize a new instance of the class. + /// + /// Called application entity title. + /// Calling application entity title. + /// AET config. + public AETConfigModel( + string calledAET, + string callingAET, + ClientAETConfig aetConfig) + { + CalledAET = calledAET; + CallingAET = callingAET; + AETConfig = aetConfig; + } + + /// + /// Clone this into a new instance of the class, optionally replacing some properties. + /// + /// Optional new CalledAET. + /// Optional new CallingAET. + /// Optional new AETConfig. + /// New AETConfigModel. + public AETConfigModel With( + string calledAET = null, + string callingAET = null, + ClientAETConfig aetConfig = null) => + new AETConfigModel( + calledAET ?? CalledAET, + callingAET ?? CallingAET, + aetConfig ?? AETConfig); + + /// + public override bool Equals(object obj) + { + return Equals(obj as AETConfigModel); + } + + /// + public bool Equals(AETConfigModel other) + { + return other != null && + CalledAET == other.CalledAET && + CallingAET == other.CallingAET && + EqualityComparer.Default.Equals(AETConfig, other.AETConfig); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(CalledAET, CallingAET, AETConfig); + } + + /// + public static bool operator ==(AETConfigModel left, AETConfigModel right) + { + return EqualityComparer.Default.Equals(left, right); + } + + /// + public static bool operator !=(AETConfigModel left, AETConfigModel right) + { + return !(left == right); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DicomExtensions.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DicomExtensions.cs new file mode 100644 index 0000000..65eacd3 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DicomExtensions.cs @@ -0,0 +1,89 @@ +namespace Microsoft.InnerEye.Listener.Common +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + + using Dicom; + + /// + /// Dicom file extension methods. + /// + public static class DicomExtensions + { + /// + /// The top level tags we want to keep. + /// TODO: Remove from the Gateway and expose in the client SDK. + /// + private static readonly IEnumerable _deAnonymizeTryAddReplaceAtTopLevel = new[] + { + // Patient module + DicomTag.PatientID, + DicomTag.PatientName, + DicomTag.PatientBirthDate, + DicomTag.PatientSex, + + // Study module + DicomTag.StudyDate, + DicomTag.StudyTime, + DicomTag.ReferringPhysicianName, + DicomTag.StudyID, + DicomTag.AccessionNumber, + DicomTag.StudyDescription, + }; + + /// + /// Creates a new DICOM file using only the tags specified in the keep DICOM tags list and without the pixel data Tag. + /// + /// The DICOM files to extract metadata from. + /// The DICOM files as a byte array with only the specified DICOM tags. + /// dicomFiles + public static IEnumerable CreateNewDicomFileWithoutPixelData(this IEnumerable dicomFiles, IEnumerable keepDicomTags) + { + dicomFiles = dicomFiles ?? throw new ArgumentNullException(nameof(dicomFiles)); + keepDicomTags = keepDicomTags ?? throw new ArgumentNullException(nameof(keepDicomTags)); + + foreach (var dicomFile in dicomFiles) + { + yield return dicomFile.CreateNewDicomFileWithoutPixelData(keepDicomTags); + } + } + + /// + /// Creates a new DICOM file with the specified keep tags and without the pixel data Tag. + /// + /// The DICOM file. + /// The keep DICOM tags. + /// The DICOM file as a byte array with only the specified DICOM tags. + /// dicomFile + public static byte[] CreateNewDicomFileWithoutPixelData(this DicomFile dicomFile, IEnumerable keepDicomTags) + { + dicomFile = dicomFile ?? throw new ArgumentNullException(nameof(dicomFile)); + keepDicomTags = keepDicomTags.Concat(_deAnonymizeTryAddReplaceAtTopLevel) ?? throw new ArgumentNullException(nameof(keepDicomTags)); + + var resultDataset = new List(); + + foreach (var dicomItem in dicomFile.Dataset) + { + if (dicomItem.Tag != DicomTag.PixelData && keepDicomTags.Contains(dicomItem.Tag)) + { + resultDataset.Add(dicomItem); + } + } + +#pragma warning disable CS0618 // We need to skip validation since + var ds = new DicomDataset() { AutoValidate = false }; +#pragma warning restore CS0618 // Type or member is obsolete + ds.Add(resultDataset); + + var extractedDicomFile = new DicomFile(ds); + + using (var memoryStream = new MemoryStream()) + { + extractedDicomFile.Save(memoryStream); + return memoryStream.ToArray(); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DryRunFolders.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DryRunFolders.cs new file mode 100644 index 0000000..7d7d56b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/DryRunFolders.cs @@ -0,0 +1,46 @@ +namespace Microsoft.InnerEye.Listener.Common +{ + using System; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + + /// + /// The dry run folders. + /// + public static class DryRunFolders + { + /// + /// The dry run feedback folder + /// + public const string DryRunAnonymisedFeedbackFolder = "DryRunFeedbackAnonymized"; + + /// + /// The dry run model folder + /// + public const string DryRunAnonymisedImageFolder = "DryRunModelAnonymizedImage"; + + /// + /// The dry run model with result folder. + /// + public const string DryRunModelWithResultFolder = "DryRunRTResultDeAnonymized"; + + /// + /// Converts the config type to a dry run folder. + /// + /// Type of the configuration. + /// The dry run folder. + /// If the config type is not a dry run type. + public static string GetFolder(AETConfigType configType) + { + switch (configType) + { + case AETConfigType.ModelDryRun: + return DryRunAnonymisedImageFolder; + case AETConfigType.ModelWithResultDryRun: + return DryRunModelWithResultFolder; + default: + throw new ArgumentException("Unknown configuration type", nameof(configType)); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Microsoft.InnerEye.Listener.Common.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Microsoft.InnerEye.Listener.Common.csproj new file mode 100644 index 0000000..aeaa54b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Microsoft.InnerEye.Listener.Common.csproj @@ -0,0 +1,37 @@ + + + net462 + x64 + Microsoft.InnerEye.Listener.Common + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Common project for Microsoft InnerEye Gateway + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/AETConfigProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/AETConfigProvider.cs new file mode 100644 index 0000000..59c6536 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/AETConfigProvider.cs @@ -0,0 +1,97 @@ +namespace Microsoft.InnerEye.Listener.Common.Providers +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using Microsoft.Extensions.Logging; + + /// + /// Monitor a JSON file containing a list of AETConfigModels. + /// + public class AETConfigProvider : BaseConfigProvider> + { + /// + /// File name for JSON file containing a list of AETConfigModels. + /// + public static readonly string AETConfigFileName = "GatewayModelRulesConfig.json"; + + /// + /// Folder name for folder containing JSON files, each containing a list of AETConfigModels. + /// + public static readonly string AETConfigFolderName = "GatewayModelRulesConfig"; + + /// + /// Initialize a new instance of the class. + /// + /// Logger. + /// Path to folder containing AETConfigFileName. + /// True to use config file, false to use folder of files. + public AETConfigProvider( + ILogger logger, + string configurationsPathRoot, + bool useFile = false) : base(logger, + Path.Combine(configurationsPathRoot, useFile ? AETConfigFileName : AETConfigFolderName)) + { + } + + /// + /// Lookup list of AETConfigModels from a JSON file. + /// + /// List of AETConfigModels. + public IEnumerable GetAETConfigs() + { + Load(); + + _t = _ts != null ? MergeModels(_ts) : _t; + + // no need to keep two copies of all the config data. + _ts = null; + + return _t; + } + + /// + /// Merge a list of lists of AET config models into one list. + /// + /// + /// Merging is only handled in one place. The algorithm is: + /// Create a new empty output list of AET config model. + /// For each input AET config model: + /// Try to find an existing AET config model in the output list with this Called and Calling AET. + /// If an existing AET config model cannot be found then copy this to the output. + /// Otherwise: append the list of ModelsConfig to the existing AET config model, ignoring all other properties, + /// and replace it in the output list. + /// + /// List of lists of AET config models. + /// List of AET config models. + private static List MergeModels(IEnumerable> modelLists) + { + var mergedModels = new List(); + + foreach (var modelList in modelLists) + { + foreach (var model in modelList) + { + var match = ApplyAETModelConfigProvider.GetAETConfigModel(mergedModels, model.CalledAET, model.CallingAET); + + if (match != null) + { + var mergedModel = match.With( + aetConfig: match.AETConfig.With( + config: match.AETConfig.Config.With( + modelsConfig: match.AETConfig.Config.ModelsConfig.Concat(model.AETConfig.Config.ModelsConfig).ToArray()))); + + mergedModels.Remove(match); + mergedModels.Add(mergedModel); + } + else + { + mergedModels.Add(model); + } + } + } + + return mergedModels; + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/ApplyAETModelConfigProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/ApplyAETModelConfigProvider.cs new file mode 100644 index 0000000..b8058c4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/ApplyAETModelConfigProvider.cs @@ -0,0 +1,265 @@ +namespace Microsoft.InnerEye.Listener.Common.Providers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Dicom; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.DicomConstraints; + + /// + /// Helper function for applying AET Model configs. + /// + public static class ApplyAETModelConfigProvider + { + /// + /// Returns the configuration for the given AET. + /// + /// List of AETConfigModels to search. + /// The DICOM AET the gateway was called + /// The DICM AET of the AE calling the gateway + /// Matching ClientAETConfig if one found, throws an exception otherwise. + public static ClientAETConfig GetAETConfigs(IEnumerable clientAETList, string calledAET, string callingAET) + { + var aetConfig = GetAETConfigModel(clientAETList, calledAET, callingAET); + if (aetConfig != null) + { + return aetConfig.AETConfig; + } + throw new Exception($"Config for called AET {calledAET} and calling AET {callingAET} not found"); + } + + /// + /// Returns the configuration for the given AET. + /// + /// List of AETConfigModels to search. + /// The DICOM AET the gateway was called + /// The DICM AET of the AE calling the gateway + /// Matching AETConfigModel if one found, null otherwise. + public static AETConfigModel GetAETConfigModel(IEnumerable clientAETList, string calledAET, string callingAET) + { + foreach (var aetConfig in clientAETList) + { + if (aetConfig.CalledAET.Equals(calledAET, System.StringComparison.Ordinal) && aetConfig.CallingAET.Equals(callingAET, System.StringComparison.Ordinal)) + { + return aetConfig; + } + } + return null; + } + + /// + /// Given a set of DICOM files and a collection of modelConfig, select the model to run and define the DICOMFiles per channel. + /// + /// + /// This is a client side only helper function. + /// If you pass dicom data spanning multiple studies this method will first group by study and only match a model + /// if all channel constraints can be satisfied from series data within an individual study. Put another way, a model + /// will only ever run on series from the same study. + /// Note however, that there is nothing to stop multiple channels in the same model being satisifed by the same series. + /// + /// The config to apply + /// The set of dicomfiles to analyse and match against a model + /// The matched segmentation model (or null) and the constraint results. + public static ConstraintResult ApplyAETModelConfig( + IEnumerable modelsConfig, + IEnumerable associationData) + { + //checks and throws exception if modelsConfig value is null + if (modelsConfig == null) + { + throw new ArgumentNullException(nameof(modelsConfig)); + } + + //checks and throws exception if associationData value is null + if (associationData == null) + { + throw new ArgumentNullException(nameof(associationData)); + } + + var dicomConstraintResults = new List(); + + // Group by StudyUID and throw away files without studyInstanceUID + var studyFilesCollection = associationData + .GroupBy(dicomFile => dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.StudyInstanceUID, string.Empty)) + .Where(g => !string.IsNullOrEmpty(g.Key)); + + foreach (var studyFiles in studyFilesCollection) + { + // Group by SeriesInstanceUID and throw away files without seriesInstanceUID + var seriesInStudyFiles = studyFiles + .GroupBy(dicomFile => dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty)) + .Where(g => !string.IsNullOrEmpty(g.Key)); + + var constraintResult = FindMatchingModelAndChannels(modelsConfig, seriesInStudyFiles); + + dicomConstraintResults.AddRange(constraintResult.DicomConstraintResults); + + // As soon as we match we return the result + if (constraintResult.Matched) + { + return new ConstraintResult(dicomConstraintResults, constraintResult.Result); + } + } + + return new ConstraintResult(dicomConstraintResults); + } + + /// + /// Searches available models and matches models where the given seriesData satisfies the model's constraints. + /// + /// Model configurations and their constrains + /// List of series from the same study to try and match against a set of models + /// The first model where the given series match the models channel constraints or null if there is not match + /// In general we should log the reasons why a series fails a constraint - and track this in the cloud. + /// If config modelsConfig has some invalid matching. E.g. matching string on a sequence + private static ConstraintResult FindMatchingModelAndChannels( + IEnumerable modelsConfig, + IEnumerable> seriesData) + { + var dicomConstraintResults = new List(); + + // Models are in priority order + foreach (var modelConstraint in modelsConfig) + { + var constraintResult = GetChannelConstraintResult(modelConstraint.ChannelConstraints, seriesData); + + dicomConstraintResults.AddRange(constraintResult.DicomConstraintResults); + + // As soon as we match we return the result + if (constraintResult.Matched) + { + return new ConstraintResult( + dicomConstraintResults, + new SegmentationModel(modelConstraint.ModelId, constraintResult.Result, modelConstraint.TagReplacements)); + } + } + + // No models matched + return new ConstraintResult(dicomConstraintResults); + } + + /// + /// Gets the channel constraint results for the collection of model channel constraints. + /// + /// The channel constraints. + /// The series data. + /// The channel data and the constraint result. + private static ConstraintResult> GetChannelConstraintResult( + IEnumerable channelConstraints, + IEnumerable> seriesData) + { + var matched = true; + + var channelData = new List(); + var dicomConstraintResults = new List(); + + foreach (var channelConstraint in channelConstraints) + { + var constraintResult = GetChannelConstraintResult(channelConstraint, seriesData); + + if (!constraintResult.Matched) + { + matched = false; + } + + channelData.Add(constraintResult.Result); + dicomConstraintResults.AddRange(constraintResult.DicomConstraintResults); + } + + return matched ? + new ConstraintResult>(dicomConstraintResults, channelData) : + new ConstraintResult>(dicomConstraintResults); + } + + /// + /// Attempts to find the first series that matches the constraints or returns a collection of DICOM constraint results. + /// + /// The channel constraints for the model. + /// The collection of Dicom files for the series. + /// If matched and the channel data or false and null and the dicom constraint result per Dicom series. + private static ConstraintResult GetChannelConstraintResult( + ModelChannelConstraints modelChannelConstraints, + IEnumerable> seriesData) + { + var dicomConstraintResults = new List(); + + foreach (var dicomSeries in seriesData) + { + var constraintResult = GetChannelConstraintResult(modelChannelConstraints, dicomSeries); + + dicomConstraintResults.AddRange(constraintResult.DicomConstraintResults); + + // As soon as we match we return the result + if (constraintResult.Matched) + { + return new ConstraintResult(dicomConstraintResults, constraintResult.Result); + } + } + + return new ConstraintResult(dicomConstraintResults); + } + + /// + /// Gets the channel constraint result for a collection of DICOM files. + /// + /// The channel constraints. + /// The dicom files. + /// The channel data and constraint result. + private static ConstraintResult GetChannelConstraintResult( + ModelChannelConstraints channelConstraints, + IEnumerable dicomFiles) + { + var (filteredDicomFiles, dicomConstraintResults) = FilterDicomFiles(channelConstraints, dicomFiles); + var filteredDicomFilesCount = filteredDicomFiles.Count; + + // Check that we have sufficient images after the filter + var filteredDicomFilesCountSufficient = + channelConstraints.MinChannelImages <= filteredDicomFilesCount && + (channelConstraints.MaxChannelImages <= 0 || filteredDicomFilesCount <= channelConstraints.MaxChannelImages); + + if (filteredDicomFilesCountSufficient) + { + // we may wish to do something more sophisticated here. e.g. record all the matches and choose + // and distribute matched series over the constraints using a heuristic + return new ConstraintResult( + dicomConstraintResults, + new ChannelData(channelID: channelConstraints.ChannelID, dicomFiles: filteredDicomFiles)); + } + + return new ConstraintResult(dicomConstraintResults); + } + + /// + /// Filters the DICOM files based on the channel constraints.. + /// + /// The channel constraints. + /// The dicom files. + /// The filtered DICOM files and the constraint results per DICOM file. + private static (IList FilteredDicomFiles, IEnumerable DicomConstraintResults) FilterDicomFiles( + ModelChannelConstraints channelConstraints, + IEnumerable dicomFiles) + { + var filteredImages = new List(); + var dicomConstraintResults = new List(); + + foreach (var dicomFile in dicomFiles) + { + var imageFilterResult = channelConstraints.ImageFilter.Check(dicomFile.Dataset); + var channelConstraintResult = channelConstraints.ChannelConstraints.Check(dicomFile.Dataset); + + dicomConstraintResults.Add(imageFilterResult); + dicomConstraintResults.Add(channelConstraintResult); + + // This check will throw InvalidOperationException if the config is incorrect + if (imageFilterResult.Result && channelConstraintResult.Result) + { + filteredImages.Add(dicomFile); + } + } + + return (filteredImages, dicomConstraintResults); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/BaseConfigProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/BaseConfigProvider.cs new file mode 100644 index 0000000..1e87f95 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/BaseConfigProvider.cs @@ -0,0 +1,107 @@ +namespace Microsoft.InnerEye.Listener.Common.Providers +{ + using System; + using System.Collections.Generic; + using System.IO; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Logging; + using Newtonsoft.Json; + + /// + /// Class that monitors a JSON settings file or folder. + /// + /// Data type underlying the JSON settings. + public class BaseConfigProvider + { + /// + /// Logger for errors loading or parsing JSON. + /// + private readonly ILogger _logger; + + /// + /// JSON file or folder name. + /// + private readonly string _settingsFileOrFolderName; + + /// + /// Cached copy of data as last loaded from JSON file. + /// + protected T _t; + + /// + /// Cached copy of data as last loaded from folder of JSON files. + /// + protected IEnumerable _ts; + + /// + /// Initialize a new instance of the class. + /// + /// Logger. + /// JSON settings file or folder. + public BaseConfigProvider( + ILogger logger, + string settingsFileOrFolderName) + { + _logger = logger; + _settingsFileOrFolderName = settingsFileOrFolderName; + } + + /// + /// Load T or Ts from a JSON file or folder. + /// + protected void Load() + { + if (File.Exists(_settingsFileOrFolderName)) + { + _ts = null; + + (_t, _) = LoadFile(_settingsFileOrFolderName); + } + else if (Directory.Exists(_settingsFileOrFolderName)) + { + _t = default(T); + var ts = new List(); + + foreach (var file in Directory.EnumerateFiles(_settingsFileOrFolderName, "*.json")) + { + var (t, loaded) = LoadFile(file); + if (loaded) + { + ts.Add(t); + } + } + + _ts = ts.ToArray(); + } + else + { + var logEntry = LogEntry.Create(ServiceStatus.NewConfigurationError, + string.Format("Settings is neither a file nor a folder: {0}", _settingsFileOrFolderName)); + logEntry.Log(_logger, LogLevel.Error); + } + } + + /// + /// Load T from a JSON file. + /// + /// Path to file. + /// Pair of T and true if file loaded correctly, false otherwise. + private (T, bool) LoadFile(string path) + { + try + { + var jsonText = File.ReadAllText(path); + + return (JsonConvert.DeserializeObject(jsonText), true); + } + catch (Exception e) + { + var logEntry = LogEntry.Create(ServiceStatus.NewConfigurationError, + string.Format("Unable to load settings file {0}", path)); + logEntry.Log(_logger, LogLevel.Error, e); + + return (default(T), false); + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayProcessorConfigProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayProcessorConfigProvider.cs new file mode 100644 index 0000000..96c65e0 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayProcessorConfigProvider.cs @@ -0,0 +1,89 @@ +namespace Microsoft.InnerEye.Listener.Common.Providers +{ + using System; + using System.IO; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.Gateway.Models; + + /// + /// Monitor a JSON file containing a GatewayProcessorConfig. + /// + public class GatewayProcessorConfigProvider : BaseConfigProvider + { + /// + /// File name for JSON file containing a GatewayProcessorConfig. + /// + public static readonly string GatewayProcessorConfigFileName = "GatewayProcessorConfig.json"; + + /// + /// Initialize a new instance of the class. + /// + /// Logger. + /// Path to folder containing GatewayProcessorConfigFileName. + public GatewayProcessorConfigProvider( + ILogger logger, + string configurationsPathRoot) : base(logger, + Path.Combine(configurationsPathRoot, GatewayProcessorConfigFileName)) + { + } + + /// + /// Load GatewayProcessorConfig from a JSON file. + /// + /// Loaded GatewayProcessorConfig. + public GatewayProcessorConfig GatewayProcessorConfig() + { + Load(); + return _t; + } + + /// + /// Load ServiceSettings from a JSON file. + /// + /// Loaded ServiceSettings. + public ServiceSettings ServiceSettings() => + GatewayProcessorConfig().ServiceSettings; + + /// + /// Load ProcessorSettings from a JSON file. + /// + /// Loaded ProcessorSettings. + public ProcessorSettings ProcessorSettings() => + GatewayProcessorConfig().ProcessorSettings; + + /// + /// Load DequeueServiceConfig from a JSON file. + /// + /// Loaded DequeueServiceConfig. + public DequeueServiceConfig DequeueServiceConfig() => + GatewayProcessorConfig().DequeueServiceConfig; + + /// + /// Load DownloadServiceConfig from a JSON file. + /// + /// Loaded DownloadServiceConfig. + public DownloadServiceConfig DownloadServiceConfig() => + GatewayProcessorConfig().DownloadServiceConfig; + + /// + /// Load ConfigurationServiceConfig from a JSON file. + /// + /// Loaded ConfigurationServiceConfig. + public ConfigurationServiceConfig ConfigurationServiceConfig() => + GatewayProcessorConfig().ConfigurationServiceConfig; + + /// + /// Create a new segmentation client based on settings in JSON file. + /// + /// Optional override license key env var for testing. + /// New IInnerEyeSegmentationClient. + public Func CreateInnerEyeSegmentationClient(string licenseKeyEnvVar = null) => + () => + { + var settings = ProcessorSettings(); + + return new InnerEyeSegmentationClient(settings.InferenceUri, licenseKeyEnvVar ?? settings.LicenseKeyEnvVar); + }; + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayReceiveConfigProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayReceiveConfigProvider.cs new file mode 100644 index 0000000..a49268b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Providers/GatewayReceiveConfigProvider.cs @@ -0,0 +1,60 @@ +namespace Microsoft.InnerEye.Listener.Common.Providers +{ + using System.IO; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Models; + + /// + /// Monitor a JSON file containing a GatewayReceiveConfig. + /// + public class GatewayReceiveConfigProvider : BaseConfigProvider + { + /// + /// File name for JSON file containing a GatewayReceiveConfig. + /// + public static readonly string GatewayReceiveConfigFileName = "GatewayReceiveConfig.json"; + + /// + /// Initialize a new instance of the class. + /// + /// Logger. + /// Path to folder containing GatewayReceiveConfigFileName. + public GatewayReceiveConfigProvider( + ILogger logger, + string configurationsPathRoot) : base(logger, + Path.Combine(configurationsPathRoot, GatewayReceiveConfigFileName)) + { + } + + /// + /// Load GatewayReceiveConfig from a JSON file. + /// + /// Loaded GatewayReceiveConfig. + public GatewayReceiveConfig GatewayReceiveConfig() + { + Load(); + return _t; + } + + /// + /// Load ServiceSettings from a JSON file. + /// + /// Loaded ServiceSettings. + public ServiceSettings ServiceSettings() => + GatewayReceiveConfig().ServiceSettings; + + /// + /// Load ConfigurationServiceConfig from a JSON file. + /// + /// Loaded ConfigurationServiceConfig. + public ConfigurationServiceConfig ConfigurationServiceConfig() => + GatewayReceiveConfig().ConfigurationServiceConfig; + + /// + /// Load ReceiveServiceConfig from a JSON file. + /// + /// Loaded ReceiveServiceConfig. + public ReceiveServiceConfig ReceiveServiceConfig() => + GatewayReceiveConfig().ReceiveServiceConfig; + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ConfigurationService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ConfigurationService.cs new file mode 100644 index 0000000..1efd55a --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ConfigurationService.cs @@ -0,0 +1,203 @@ + +namespace Microsoft.InnerEye.Listener.Common.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Security.Authentication; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.Models; + + /// + /// The receive configuration service, used for starting and stopping the receive service base on configuration changes, + /// + /// + public sealed class ConfigurationService : ThreadedServiceBase + { + /// + /// Callback to create InnerEye segmentation client. + /// + private readonly Func _getInnerEyeSegmentationClient; + + /// + /// The InnerEye segmentation client. + /// + private IInnerEyeSegmentationClient _innerEyeSegmentationClient; + + /// + /// The services this instance manages. + /// + private readonly List _services; + + /// + /// The get gateway configuration function. + /// + private readonly Func _getConfigurationServiceConfig; + + /// + /// The current configuration service configuration. + /// + private ConfigurationServiceConfig _configurationServiceConfig; + + /// + /// Initializes a new instance of the class. + /// + /// Callback to create InnerEye segmentation client. + /// Configuration service config callback. + /// The log. + /// The services. + /// getGatewayConfigurationFunction, services or getGatewayConfigurationFunction + public ConfigurationService( + Func getInnerEyeSegmentationClient, + Func getConfigurationServiceConfig, + ILogger logger, + params IService[] services) + : base(logger, 1) + { + _getInnerEyeSegmentationClient = getInnerEyeSegmentationClient; + _getConfigurationServiceConfig = getConfigurationServiceConfig ?? throw new ArgumentNullException(nameof(getConfigurationServiceConfig)); + + _services = services.ToList() ?? throw new ArgumentNullException(nameof(services)); + _services.ForEach(x => x.StopRequested += Service_StopRequested); + } + + /// + /// Called when the service is started. + /// + protected override void OnServiceStart() + { + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = null; + + if (_getInnerEyeSegmentationClient != null) + { + _innerEyeSegmentationClient = _getInnerEyeSegmentationClient.Invoke(); + + Task.WaitAll(PingAsync(stopServiceOnAuthFailures: false)); + } + + _configurationServiceConfig = _getConfigurationServiceConfig(); + + // Initialize the log + LogInformation(LogEntry.CreateInitialize()); + + // Start the services. + _services.ForEach(x => x.Start()); + } + + /// + /// Called when [service stop]. + /// + protected override void OnServiceStop() + { + _services.ForEach(x => x.OnStop()); + } + + /// + /// Called when [update tick] is called. This will wait for all work to execute then will pause for desired interval delay. + /// + /// The cancellation token. + /// + /// The async task. + /// + protected override async Task OnUpdateTickAsync(CancellationToken cancellationToken) + { + // Check we can still ping with the license key + // This call will stop this service if the license key is invalid. + await PingAsync(); + + await Task.Delay(_configurationServiceConfig.ConfigurationRefreshDelay, cancellationToken); + + var config = _getConfigurationServiceConfig(); + + if (config.ConfigCreationDateTime > _configurationServiceConfig.ConfigCreationDateTime + && DateTime.UtcNow >= config.ApplyConfigDateTime) + { + LogInformation(LogEntry.Create(ServiceStatus.NewConfigurationAvailable)); + + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = null; + + if (_getInnerEyeSegmentationClient != null) + { + _innerEyeSegmentationClient = _getInnerEyeSegmentationClient.Invoke(); + } + + // Update the current configuration. + _configurationServiceConfig = config; + + // Stop the services + _services.ForEach(x => x.OnStop()); + + // Re-initialize the log + LogInformation(LogEntry.CreateInitialize()); + + // Start the services again + _services.ForEach(x => x.Start()); + + LogInformation(LogEntry.Create(ServiceStatus.NewConfigurationApplied)); + } + } + + /// + /// Disposes of all managed resources. + /// + /// If we are disposing. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + { + return; + } + + _services.ForEach(x => x.StopRequested -= Service_StopRequested); + _services.ForEach(x => x.Dispose()); + + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = null; + } + + /// + /// Pings the segmentation client and stops the service on authentication exceptions. + /// + /// Will stop the service on any authentication failures. + /// If the license key is incorrect for the segmentation client. + private async Task PingAsync(bool stopServiceOnAuthFailures = true) + { + try + { + if (_innerEyeSegmentationClient != null) + { + await _innerEyeSegmentationClient.PingAsync(); + } + } + catch (AuthenticationException e) + { + LogError(LogEntry.Create(ServiceStatus.PingError), e); + + if (stopServiceOnAuthFailures) + { + StopServiceAsync(); + } + + throw; + } + } + + /// + /// Handles the StopRequested event of the Service control. + /// + /// The source of the event. + /// The instance containing the event data. + private void Service_StopRequested(object sender, EventArgs e) + { + StopServiceAsync(); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/DequeueClientServiceBase.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/DequeueClientServiceBase.cs new file mode 100644 index 0000000..b15484c --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/DequeueClientServiceBase.cs @@ -0,0 +1,301 @@ +namespace Microsoft.InnerEye.Listener.Common.Services +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + + /// + /// The service base for any service that will dequeue items from a message queue. + /// + /// + public abstract class DequeueClientServiceBase : ThreadedServiceBase where T : QueueItemBase + { + /// + /// Callback for gateway processor config. + /// + private Func _getDequeueServiceConfig; + + /// + /// The dequeue queue path. + /// + private readonly string _dequeueQueuePath; + + /// + /// The dead letter queue path. + /// + private readonly string _deadLetterQueuePath; + + /// + /// Dequeue service config. + /// + private DequeueServiceConfig _dequeueServiceConfig; + + /// + /// The last time the dead letter messages were moved from the dead letter queue to the dequeue queue. + /// + private DateTime _lastDeadLetterMove; + + /// + /// Create a new IMessageQueue for the dead letter queue. + /// + /// new IMessageQueue. + public IMessageQueue DequeueMessageQueue => GatewayMessageQueue.Get(_dequeueQueuePath); + + /// + /// Create a new IMessageQueue for the dead letter queue. + /// + /// new IMessageQueue. + public IMessageQueue DeadletterMessageQueue => GatewayMessageQueue.Get(_deadLetterQueuePath); + + /// + /// Initializes a new instance of the class. + /// + /// Callback for dequeue service config. + /// The dequeue queue path. + /// The listener log. + /// The number of concurrent execution instances we should have. + protected DequeueClientServiceBase( + Func getDequeueServiceConfig, + string dequeueQueuePath, + ILogger logger, + int instances) + : base(logger, instances) + { + _getDequeueServiceConfig = getDequeueServiceConfig ?? throw new ArgumentNullException(nameof(getDequeueServiceConfig)); + + // We create the dequeue and dead letter queue on construction. This can throw exceptions. + _dequeueQueuePath = !string.IsNullOrWhiteSpace(dequeueQueuePath) ? dequeueQueuePath : throw new ArgumentException("The dequeue queue path is null or white space.", nameof(dequeueQueuePath)); + _deadLetterQueuePath = DequeueServiceConfig.DeadLetterQueuePath(dequeueQueuePath); + } + + /// + /// Creates a new queue transaction. + /// + /// The queue transaction. + protected IQueueTransaction CreateQueueTransaction() => CreateQueueTransaction(_dequeueQueuePath); + + /// + /// Called when the service is started. + /// + protected override void OnServiceStart() + { + _dequeueServiceConfig = _getDequeueServiceConfig(); + } + + /// + /// Called when [service stop]. + /// + protected override void OnServiceStop() + { + } + + /// + /// Dequeues the next message from the queue. + /// Note: This method will not commit or abort the queue transaction. + /// + /// The queue transaction. + /// The cancellation token. + /// The de-queued object. + /// If the queue path is not correct. + /// The queue timed out when reading. + /// The queue does not have permissions to read. + protected async Task DequeueNextMessageAsync( + IQueueTransaction queueTransaction, + CancellationToken cancellationToken) + { + // Check if we should move all the dead letter messages on every dequeue. + CheckMoveDeadLetterMessages(); + + try + { + // Note: We do not put the message queue in a using block. The service manages the lifecycle + // of message queues. + var messageItem = GetMessageQueue(_dequeueQueuePath) + .DequeueNextMessage(queueTransaction); + + // Increase the count on the message. + // Note: We should not check if the maximum dequeue count has been reached here. We should do that on handling exceptions. + messageItem.DequeueCount++; + + LogInformation(LogEntry.Create(MessageQueueStatus.Dequeued, + queueItemBase: messageItem, + sourceMessageQueuePath: _dequeueQueuePath)); + + return messageItem; + } + catch (MessageQueueReadException) + { + // Delay here before throw the exception up the next level to delay message queue reads. + await Task.Delay(DequeueServiceConfig.DequeueTimeout, cancellationToken); + throw; + } + catch (MessageQueuePermissionsException e) + { + LogError(LogEntry.Create(MessageQueueStatus.DequeueError, sourceMessageQueuePath: _dequeueQueuePath), + e); + + // We cannot recover from access violations. We should stop the service. + StopServiceAsync(); + + throw; + } + } + + /// + /// Handles the intended behaviour when an unknown exception occurs when processing a queue item. + /// We currently de-queue the item and put to the back of the queue. If we have attempted to process this + /// item more than [X] times the item is removed from the queue and added to the dead letter queue (if the maximum age has not been reached). + /// + /// The queue item. + /// The message queue transaction. + /// Action when the queue item is old and will be removed from the queues (including the dead letter queue). + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2")] + protected void HandleExceptionForTransaction(T queueItem, IQueueTransaction queueTransaction, Action oldQueueItemAction = null) + { + if (queueTransaction == null) + { + throw new ArgumentNullException(nameof(queueTransaction), "The queue transaction is null."); + } + + // The queue item is null, commit the transaction. + if (queueItem == null) + { + oldQueueItemAction?.Invoke(); + queueTransaction.Commit(); + + return; + } + + try + { + var isMessageOld = DateTime.UtcNow - queueItem.AssociationDateTime > _dequeueServiceConfig.MaximumQueueMessageAge; + + // If the item is not null and we haven't dequeued too many times, add to the queue at the back + if (queueItem.DequeueCount < DequeueServiceConfig.MaxDequeueCount && !isMessageOld) + { + EnqueueMessage(queueItem, _dequeueQueuePath, queueTransaction); + } + // Enqueue onto the dead letter queue if the message isn't old + else if (!isMessageOld) + { + EnqueueMessage(queueItem, _deadLetterQueuePath, queueTransaction); + + LogInformation(LogEntry.Create(MessageQueueStatus.EnqueuedDeadLetter, + queueItemBase: queueItem, + sourceMessageQueuePath: _deadLetterQueuePath)); + } + // Remove from all queues if the dequeue count has been reached and the message is old. Invoke any clean-up + // tasks in the action. + else + { + // Make sure this is called before commiting the transaction. This will most likely use the transaction + // to enqueue to the delete service queue. + oldQueueItemAction?.Invoke(); + + LogError(LogEntry.Create(MessageQueueStatus.TooOldForDeadLetterError, + queueItemBase: queueItem, + sourceMessageQueuePath: _dequeueQueuePath), + new Exception("Message dequeued too many times. Remove from all queues.")); + } + + queueTransaction.Commit(); + } + catch (Exception e) + { + LogError(LogEntry.Create(MessageQueueStatus.TransactionExceptionHandlerError, + queueItemBase: queueItem, + sourceMessageQueuePath: _dequeueQueuePath), + e); + + queueTransaction.Abort(); + } + } + + /// + /// Checks to see if we should move the dead letter messages back to the parent queue. + /// + /// If the queue path is not correct. + protected void CheckMoveDeadLetterMessages() + { + if (DateTime.UtcNow < _lastDeadLetterMove + _dequeueServiceConfig.DeadLetterMoveFrequency) + { + return; + } + + // Move messages from dead letter queue to parent queue + MoveQueueMessages(_deadLetterQueuePath, _dequeueQueuePath); + + _lastDeadLetterMove = DateTime.UtcNow; + } + + /// + /// Moves the queue messages from the source queue to the destination queue. + /// + /// The source queue path. + /// The destination queue path. + /// If the queue path is not correct. + private void MoveQueueMessages(string sourceQueuePath, string destinationQueuePath) + { + // Move messages from the source queue to the destination + var sourceQueue = GetMessageQueue(sourceQueuePath); + var destinationQueue = GetMessageQueue(destinationQueuePath); + + var moreMessages = true; + + // Keep de-queueing until no more messages on the dead letter queue + while (moreMessages) + { + // Create a new transaction for-each move request + using (var transaction = CreateQueueTransaction(sourceQueuePath)) + { + transaction.Begin(); + + try + { + var message = sourceQueue.DequeueNextMessage(transaction); + + LogInformation(LogEntry.Create(MessageQueueStatus.Moved, + queueItemBase: message, + destinationMessageQueuePath: destinationQueuePath, + sourceMessageQueuePath: sourceQueuePath)); + + destinationQueue.Enqueue(message, transaction); + transaction.Commit(); + } + catch (MessageQueueReadException) + { + // No more messages + moreMessages = false; + transaction.Commit(); + } + catch (MessageQueueWriteException e) + { + LogError(LogEntry.Create(MessageQueueStatus.MoveError, + information: "Failed to wite a message from the dead letter queue to origin queue", + destinationMessageQueuePath: destinationQueuePath, + sourceMessageQueuePath: sourceQueuePath), + e); + // Abort on a write exception + transaction.Abort(); + } + catch (Exception e) + { + LogError(LogEntry.Create(MessageQueueStatus.MoveError, + information: "Failed to dequeue a message from the dead letter queue", + destinationMessageQueuePath: destinationQueuePath, + sourceMessageQueuePath: sourceQueuePath), + e); + + transaction.Commit(); + } + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/IService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/IService.cs new file mode 100644 index 0000000..86011dc --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/IService.cs @@ -0,0 +1,25 @@ +namespace Microsoft.InnerEye.Listener.Common.Services +{ + using System; + + /// + /// Wrapped windows service interface. + /// + public interface IService : IDisposable + { + /// + /// Called when the service wishes to stop. + /// + event EventHandler StopRequested; + + /// + /// Starts the service. + /// + void Start(); + + /// + /// Called when the service is stopping. + /// + void OnStop(); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceHelpers.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceHelpers.cs new file mode 100644 index 0000000..fc92bd8 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceHelpers.cs @@ -0,0 +1,49 @@ +namespace Microsoft.InnerEye.Listener.Common.Services +{ + using System; + using System.Net; + using System.ServiceProcess; + using System.Threading; + using Microsoft.InnerEye.Gateway.Models; + + /// + /// Helpers for window services. + /// + public static class ServiceHelpers + { + /// + /// Helper for running services. As it is not possible to debug windows services + /// in debug mode we start a thread manually for each service. + /// + /// The service name. + /// Service settings. + /// The services to start. + /// + public static void RunServices(string serviceName, ServiceSettings serviceSettings, params IService[] services) + { + if (services == null || services.Length == 0) + { + throw new ArgumentException("Must provided at least one service to run.", nameof(services)); + } + + if (serviceSettings.RunAsConsole) + { + ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + foreach (var service in services) + { + new Thread(() => { service.Start(); }).Start(); + } + + Thread.Sleep(TimeSpan.FromDays(1)); + } + else + { + using (var serviceWrapper = new ServiceWrapper(serviceName, services)) + { + ServiceBase.Run(serviceWrapper); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceNames.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceNames.cs new file mode 100644 index 0000000..6a7441e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceNames.cs @@ -0,0 +1,18 @@ +namespace Microsoft.InnerEye.Listener.Common.Services +{ + /// + /// Service names. + /// + public static class ServiceNames + { + /// + /// The service name. + /// + public const string ProcessorServiceName = "GatewayProcessorService"; + + /// + /// The service name. + /// + public const string ReceiveServiceName = "GatewayReceiveService"; + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceWrapper.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceWrapper.cs new file mode 100644 index 0000000..5659e7b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ServiceWrapper.cs @@ -0,0 +1,98 @@ +namespace Microsoft.InnerEye.Listener.Common.Services +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Globalization; + using System.Linq; + using System.ServiceProcess; + + /// + /// Windows service wrapper so we can unit test window services. + /// + public class ServiceWrapper : ServiceBase + { + /// + /// The services. + /// + private readonly List _services; + + /// + /// Constructor. + /// + /// The service to start. + public ServiceWrapper(string serviceName, params IService[] services) + { + _services = services.ToList(); + _services.ForEach(x => x.StopRequested += Service_StopRequested); + + ServiceName = serviceName; + } + + /// + /// Overrides the on start method and calls the service start method. + /// + /// The start arguments. + protected override void OnStart(string[] args) + { + Trace.TraceInformation(FormatLogStatement(nameof(OnStart))); + + base.OnStart(args); + + _services.ForEach(x => x.Start()); + } + + /// + /// Overrides the on stop method and calls stop on the service. + /// + protected override void OnStop() + { + Trace.TraceInformation(FormatLogStatement(nameof(OnStop))); + + base.OnStop(); + + _services.ForEach(x => x.OnStop()); + } + + /// + /// Overrides the dispose method and calls dispose on the service. + /// + /// If we are dispsoing. + protected override void Dispose(bool disposing) + { + Trace.TraceInformation(FormatLogStatement(nameof(Dispose))); + + base.Dispose(disposing); + + if (disposing) + { + foreach (var service in _services) + { + service.StopRequested -= Service_StopRequested; + service.Dispose(); + } + } + } + + /// + /// Called when the service wishes to stop. + /// + /// The sender object. + /// The event args. + private void Service_StopRequested(object sender, EventArgs e) + { + if (CanStop) + { + Stop(); + } + } + + /// + /// Gets for formatted statement for logging. + /// + /// The inner statement. + /// The formatted log statement. + private string FormatLogStatement(string value) => + string.Format(CultureInfo.InvariantCulture, "[{0}] {1}.", ServiceName, value); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ThreadedServiceBase.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ThreadedServiceBase.cs new file mode 100644 index 0000000..eace82e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Common/Services/ThreadedServiceBase.cs @@ -0,0 +1,593 @@ +namespace Microsoft.InnerEye.Listener.Common.Services +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + + /// + /// Base class for a service that can have multiple main worker loops (instances) that are executed on a separate task. + /// + /// + public abstract class ThreadedServiceBase : IService + { + /// + /// Logger. + /// + private readonly ILogger _logger; + + /// + /// The message queues. + /// + private readonly Dictionary _messageQueues = new Dictionary(); + + /// + /// The execution tasks. + /// + private readonly Task[] _executionTasks; + + /// + /// The cancellation token source. + /// + private CancellationTokenSource _cancellationTokenSource; + + /// + /// The start count. This is volatile as this property is mainly used for testing and will be accessed from a different task. + /// + private volatile int _startCount; + + /// + /// If this instance is disposed. + /// + private bool _isDisposed = false; + + /// + /// If the main execution thread is running. + /// + private bool _isRunning = false; + + /// + /// The software version string for appending to Dicom files the Gateway verison that modified/ saved the file. + /// + private readonly string _softwareVersionString; + + /// + /// Initializes a new instance of the class. + /// + /// The log. + /// The number of concurrent execution instances we should have. + protected ThreadedServiceBase( + ILogger logger, + int instances) + { + _logger = logger ?? throw new ArgumentNullException(nameof(logger), "The log instance is null."); + _executionTasks = new Task[instances]; + + var softwareVersion = Assembly.GetExecutingAssembly().GetName().Version; + _softwareVersionString = string.Format(CultureInfo.InvariantCulture, "Microsoft InnerEye Gateway: {0}", softwareVersion); + } + + /// + /// Called when the service wishes to stop. + /// + public event EventHandler StopRequested; + + /// + /// Gets a value indicating whether this instance is execution thread running. + /// + /// + /// true if this instance is execution thread running; otherwise, false. + /// + public bool IsExecutionThreadRunning => _isRunning; + + /// + /// Gets the number of times this instace has started. + /// + /// + /// The number of times this instance has started. + /// + public int StartCount => _startCount; + + /// + /// Starts the service. + /// + public void Start() + { + _cancellationTokenSource = new CancellationTokenSource(); + + try + { + LogInformation(LogEntry.Create(ServiceStatus.Starting)); + + // Start the service + OnServiceStart(); + + // Note: We should only start-up the worker task and return. We should not execute any long running operations here. + for (var i = 0; i < _executionTasks.Length; i++) + { + _executionTasks[i] = Task.Run(() => Execute(_cancellationTokenSource.Token), _cancellationTokenSource.Token); + _startCount++; + } + + _isRunning = true; + + LogInformation(LogEntry.Create(ServiceStatus.Started)); + } + catch (Exception e) + { + LogError(LogEntry.Create(ServiceStatus.StartError), e); + + // Stop the service on any start-up failure + StopServiceAsync(); + } + } + + /// + /// Called when the service is stopping. + /// + public void OnStop() + { + LogInformation(LogEntry.Create(ServiceStatus.Stopping)); + + try + { + _cancellationTokenSource?.Cancel(); + } + catch (Exception e) + { + LogError(LogEntry.Create(ServiceStatus.StoppingError, + information: "Cancellation token source cancel exception."), + e); + } + + try + { + // Will only wait 10 seconds for all tasks to finish nicely + Task.WaitAll(_executionTasks.Where(x => x != null).ToArray(), TimeSpan.FromSeconds(10)); + } + catch (Exception e) + { + LogError(LogEntry.Create(ServiceStatus.StoppingError, + information: "Unknown exception waiting for all execution tasks to end."), + e); + } + + OnServiceStop(); + + _isRunning = false; + + LogInformation(LogEntry.Create(ServiceStatus.Stopped)); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Disposes of all managed resources. + /// + /// If we are disposing. + protected virtual void Dispose(bool disposing) + { + if (_isDisposed || !disposing) + { + return; + } + + OnStop(); + + if (_cancellationTokenSource != null) + { + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + } + + foreach (var queue in _messageQueues) + { + try + { + queue.Value.Dispose(); + } + catch (Exception e) + { + LogError(LogEntry.Create(MessageQueueStatus.DisposeError, + sourceMessageQueuePath: queue.Value.QueuePath), + e); + } + } + + _messageQueues.Clear(); + + _isDisposed = true; + } + + /// + /// Call when you would like this service instance to stop. + /// + protected void StopServiceAsync() + { + _cancellationTokenSource.Cancel(); + Task.Run(() => StopRequested?.Invoke(this, new EventArgs())); + } + + /// + /// Called when the service is started. + /// + protected abstract void OnServiceStart(); + + /// + /// Called when [service stop]. + /// + protected abstract void OnServiceStop(); + + /// + /// Called when [update tick] is called. This will wait for all work to execute then will pause for desired interval delay. + /// + /// The cancellation token. + /// The async task. + protected abstract Task OnUpdateTickAsync(CancellationToken cancellationToken); + + /// + /// Enqueues the message using its own message queue transaction. + /// + /// The enqueue message type. + /// The message to enqueue. + /// The queue path to enqueue the message onto. + protected void EnqueueMessage(TMessageType message, string messageQueuePath) where TMessageType : QueueItemBase + { + using (var transaction = CreateQueueTransaction(messageQueuePath)) + { + BeginMessageQueueTransaction(transaction); + + try + { + EnqueueMessage(message, messageQueuePath, transaction); + transaction.Commit(); + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.BaseEnqueueMessageError, + queueItemBase: message), + e); + transaction.Abort(); + } + } + } + + /// + /// Enqueues the message onto the message queue. + /// Note: This method will not commit or abort the queue transaction. + /// + /// The enqueue message type. + /// The message to enqueue. + /// The queue path to enqueue the message onto. + /// The queue transaction. + /// If the queue path is not correct. + /// If we do not have permissions to write to the message queue. + /// If the message queue could not write to the queue. + protected void EnqueueMessage( + TMessageType message, + string messageQueuePath, + IQueueTransaction queueTransaction) where TMessageType : QueueItemBase + { + try + { + GetMessageQueue(messageQueuePath).Enqueue(message, queueTransaction); + + LogInformation(LogEntry.Create(MessageQueueStatus.Enqueued, + queueItemBase: message, + sourceMessageQueuePath: messageQueuePath)); + } + catch (MessageQueuePermissionsException e) + { + LogError(LogEntry.Create(MessageQueueStatus.EnqueueError, + queueItemBase: message, + sourceMessageQueuePath: messageQueuePath), + e); + + // We cannot recover from access violations. We should stop the service. + StopServiceAsync(); + + throw; + } + } + + /// + /// Begins the message queue transaction. + /// MessageQueueTransaction.Begin() can throw exceptions. This method wraps the retry logic. + /// + /// The queue transaction. + /// The time delay between every retry. + /// The maximum number of retries. + /// If the transaction has already been started. + /// If we failed to start a transaction after retrying. + protected void BeginMessageQueueTransaction(IQueueTransaction queueTransaction, int retrySeconds = 30, int maximumRetry = 120) + { + if (queueTransaction == null) + { + throw new ArgumentNullException(nameof(queueTransaction), "The queue transaction is null."); + } + + try + { + queueTransaction.Begin(); + return; + } + catch (MessageQueueTransactionBeginException e) + { + LogError(LogEntry.Create(MessageQueueStatus.BeginTransactionError), + e); + + // Throw if we reach the retry limit or cancellation has been requested (service stop) + if (maximumRetry <= 0 || _cancellationTokenSource.IsCancellationRequested) + { + throw; + } + } + + // Delay before retrying + _cancellationTokenSource.Token.WaitHandle.WaitOne(TimeSpan.FromSeconds(retrySeconds)); + + BeginMessageQueueTransaction(queueTransaction, retrySeconds, maximumRetry - 1); + } + + /// + /// Creates a new queue transaction. + /// + /// The queue path. + /// The queue transaction. + protected IQueueTransaction CreateQueueTransaction(string messageQueuePath) + { + return GetMessageQueue(messageQueuePath).CreateQueueTransaction(); + } + + /// + /// Gets a message queue or throws an exception. + /// + /// The message queue path. + /// The message queue. + /// If any exception occured when getting the message queue. + protected IMessageQueue GetMessageQueue(string messageQueuePath) + { + if (!_messageQueues.ContainsKey(messageQueuePath)) + { + try + { + var messageQueue = GatewayMessageQueue.Get(messageQueuePath); + + LogInformation(LogEntry.Create(MessageQueueStatus.InitialisedQueue, + sourceMessageQueuePath: messageQueuePath)); + + _messageQueues[messageQueuePath] = messageQueue; + } + catch (Exception e) + { + LogError(LogEntry.Create(MessageQueueStatus.InitialiseQueueError, + sourceMessageQueuePath: messageQueuePath), + e); + + throw; + } + } + + return _messageQueues[messageQueuePath]; + } + + /// + /// Attemps to save all the Dicom files to disk into the directory path with a new Guid per file. + /// + /// The directory to save the files to disk. + /// The Dicom files to write to disk. + /// The collection of saved file paths. + /// If the dicom files are null. + /// If the folder path could not be created. + protected Task> SaveDicomFilesAsync(string directory, params DicomFile[] dicomFiles) + { + return SaveDicomFilesAsync(directory, (IEnumerable)dicomFiles); + } + + /// + /// Attemps to save all the Dicom files to disk into the directory path with a new Guid per file. + /// + /// The directory to save the files to disk. + /// The Dicom files to write to disk. + /// The collection of saved file paths. + /// If the dicom files are null. + /// If the folder path could not be created. + protected async Task> SaveDicomFilesAsync(string directory, IEnumerable dicomFiles) + { + if (dicomFiles == null) + { + throw new ArgumentNullException(nameof(dicomFiles)); + } + + var filePaths = new List(); + + var directoryInfo = new DirectoryInfo(directory); + + if (!directoryInfo.Exists) + { + directoryInfo.Create(); + } + + foreach (var dicomFile in dicomFiles) + { + var filePath = $@"{directory}\{Guid.NewGuid()}.dcm"; + + // Note: Whenever we save a file, we append our software version number. + var newSoftwareVersions = new List() { _softwareVersionString }; + if (dicomFile.Dataset.TryGetValues(DicomTag.SoftwareVersions, out var oldValues)) + { + newSoftwareVersions.AddRange(oldValues); + } + + dicomFile.Dataset.AddOrUpdate(DicomTag.SoftwareVersions, string.Join(@"\", newSoftwareVersions)); + + await dicomFile.SaveAsync(filePath); + + filePaths.Add(filePath); + } + + return filePaths; + } + + /// + /// Enumerates all files in a directory. + /// + /// The directory path. + /// Queue item base. + /// The collection of file paths in a directory. + protected IEnumerable EnumerateFiles(string directoryPath, QueueItemBase queueItemBase) + { + if (string.IsNullOrWhiteSpace(directoryPath)) + { + throw new ArgumentException("The directory path is null or white space.", nameof(directoryPath)); + } + + try + { + return Directory.EnumerateFiles(directoryPath); + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.BaseEnumerateDirectoryError, + queueItemBase: queueItemBase, + path: directoryPath), + e); + } + + return new string[0]; + } + + /// + /// Reads all dicom files in a directory. + /// + /// The directory path. + /// Queue item base. + /// The collection of DICOM files. + /// directoryPath + protected IEnumerable ReadDicomFiles(string directoryPath, QueueItemBase queueItemBase) + { + if (string.IsNullOrWhiteSpace(directoryPath)) + { + throw new ArgumentException("The directory path is null or white space.", nameof(directoryPath)); + } + + return ReadDicomFiles(EnumerateFiles(directoryPath, queueItemBase), queueItemBase); + } + + /// + /// Reads all the DICOM files from a collection of file paths. + /// + /// The file paths. + /// Queue item base. + /// The collection of DICOM files. + protected IEnumerable ReadDicomFiles(IEnumerable filePaths, QueueItemBase queueItemBase) + { + foreach (var filePath in filePaths) + { + var dicomFile = TryOpenDicomFile(filePath, queueItemBase); + + if (dicomFile != null) + { + yield return dicomFile; + } + } + } + + /// + /// Tries to open a dicom file and returns null if it fails. + /// + /// The file path. + /// Queue item base. + /// The DICOM file or null. + protected DicomFile TryOpenDicomFile(string filePath, QueueItemBase queueItemBase) + { + if (string.IsNullOrWhiteSpace(filePath)) + { + throw new ArgumentNullException(nameof(filePath)); + } + + try + { + return DicomFile.Open(filePath); + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.BaseOpenDicomFileError, + queueItemBase: queueItemBase, + path: filePath), + e); + } + + return null; + } + + /// + /// Entry point for the worker thread. This invokes the update method and pauses depending on the defined interval. + /// + /// The cancellation token. + private void Execute(CancellationToken cancellationToken) + { + // Main execution loop - if we encounter an exception during start-up, this will be false + while (!cancellationToken.IsCancellationRequested) + { + try + { + OnUpdateTickAsync(cancellationToken).Wait(cancellationToken); + } + catch (OperationCanceledException e) + { + if (!cancellationToken.IsCancellationRequested) + { + // If we got this error when the cancellation token has not been set, this needs to be logged. + // Otherwise, the service is shutting down normally and the wait has thrown an operation canceled exception. + LogError(LogEntry.Create(ServiceStatus.ExecuteError), e); + } + } + catch (Exception e) + { + LogError(LogEntry.Create(ServiceStatus.ExecuteError), e); + } + } + } + + /// + /// Logs the event at information level. + /// + /// Log entry. + protected void LogInformation(LogEntry logEntry) => + logEntry.Log(_logger, LogLevel.Information); + + /// + /// Logs the event at trace level. + /// + /// Log entry. + protected void LogTrace(LogEntry logEntry) => + logEntry.Log(_logger, LogLevel.Trace); + + /// + /// Logs the error. + /// + /// Log entry. + /// Exception. + protected void LogError(LogEntry logEntry, Exception exception) => + logEntry.Log(_logger, LogLevel.Error, exception); + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Microsoft.InnerEye.Listener.Processor.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Microsoft.InnerEye.Listener.Processor.csproj new file mode 100644 index 0000000..17de4fb --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Microsoft.InnerEye.Listener.Processor.csproj @@ -0,0 +1,47 @@ + + + Exe + net462 + x64 + Microsoft.InnerEye.Listener.Processor + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Processor Windows Service for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + Always + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Program.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Program.cs new file mode 100644 index 0000000..aafa5a4 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Program.cs @@ -0,0 +1,82 @@ +namespace Microsoft.InnerEye.Listener.Processor +{ + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Listener.Common.Providers; + using Microsoft.InnerEye.Listener.Common.Services; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.Processor.Services; + + public static class Program + { + /// + /// The service name. + /// + public const string ServiceName = ServiceNames.ProcessorServiceName; + + /// + /// The main entry point for the application. + /// + public static void Main() + { + var configurationsPathRoot = "../../../../../SampleConfigurations"; + + // Create the loggerFactory as Console + Log4Net. + using (var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + builder.SetMinimumLevel(LogLevel.Trace); + builder.AddLog4Net(); + })) + { + var aetConfigurationProvider = new AETConfigProvider( + loggerFactory.CreateLogger("ModelSettings"), + configurationsPathRoot); + + var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider( + loggerFactory.CreateLogger("ProcessorSettings"), + configurationsPathRoot); + + // The ProjectInstaller.cs uses the service name to install the service. + // If you change it please update the ProjectInstaller.cs + ServiceHelpers.RunServices( + ServiceName, + gatewayProcessorConfigProvider.ServiceSettings(), + new ConfigurationService( + gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(), + gatewayProcessorConfigProvider.ConfigurationServiceConfig, + loggerFactory.CreateLogger("ConfigurationService"), + new UploadService( + gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(), + aetConfigurationProvider.GetAETConfigs, + GatewayMessageQueue.UploadQueuePath, + GatewayMessageQueue.DownloadQueuePath, + GatewayMessageQueue.DeleteQueuePath, + gatewayProcessorConfigProvider.DequeueServiceConfig, + loggerFactory.CreateLogger("UploadService"), + instances: 2), + new DownloadService( + gatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(), + GatewayMessageQueue.DownloadQueuePath, + GatewayMessageQueue.PushQueuePath, + GatewayMessageQueue.DeleteQueuePath, + gatewayProcessorConfigProvider.DownloadServiceConfig, + gatewayProcessorConfigProvider.DequeueServiceConfig, + loggerFactory.CreateLogger("DownloadService"), + instances: 1), + new PushService( + aetConfigurationProvider.GetAETConfigs, + new DicomDataSender(), + GatewayMessageQueue.PushQueuePath, + GatewayMessageQueue.DeleteQueuePath, + gatewayProcessorConfigProvider.DequeueServiceConfig, + loggerFactory.CreateLogger("PushService"), + instances: 1), + new DeleteService( + GatewayMessageQueue.DeleteQueuePath, + gatewayProcessorConfigProvider.DequeueServiceConfig, + loggerFactory.CreateLogger("DeleteService")))); + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.Designer.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.Designer.cs new file mode 100644 index 0000000..ba6d997 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.Designer.cs @@ -0,0 +1,60 @@ +namespace Microsoft.InnerEye.Listener.Processor +{ + partial class ProjectInstaller + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.ProcessorServiceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller(); + this.ProcessorServiceInstaller = new System.ServiceProcess.ServiceInstaller(); + // + // ProcessorServiceProcessInstaller + // + this.ProcessorServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem; + this.ProcessorServiceProcessInstaller.Password = null; + this.ProcessorServiceProcessInstaller.Username = null; + // + // ProcessorServiceInstaller + // + this.ProcessorServiceInstaller.DisplayName = "InnerEye Gateway Processor Service"; + this.ProcessorServiceInstaller.ServiceName = Program.ServiceName; + this.ProcessorServiceInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic; + this.ProcessorServiceInstaller.DelayedAutoStart = true; + // + // ProjectInstaller + // + this.Installers.AddRange(new System.Configuration.Install.Installer[] { + this.ProcessorServiceProcessInstaller, + this.ProcessorServiceInstaller}); + + } + + #endregion + + private System.ServiceProcess.ServiceProcessInstaller ProcessorServiceProcessInstaller; + private System.ServiceProcess.ServiceInstaller ProcessorServiceInstaller; + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.cs new file mode 100644 index 0000000..34abc3b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.cs @@ -0,0 +1,65 @@ +namespace Microsoft.InnerEye.Listener.Processor +{ + using System; + using System.Collections; + using System.ComponentModel; + using System.Configuration.Install; + using System.Diagnostics; + using System.Globalization; + using System.ServiceProcess; + + /// + /// The project installer class. + /// + /// + [RunInstaller(true)] + public partial class ProjectInstaller : Installer + { + /// + /// Initializes a new instance of the class. + /// + public ProjectInstaller() + { + InitializeComponent(); + } + + /// + /// Raises the event. + /// + /// An that contains the state of the computer before the installers in the property uninstall their installations. + protected override void OnBeforeUninstall(IDictionary savedState) + { + using (var controller = new ServiceController(Program.ServiceName)) + { + if (controller.Status == ServiceControllerStatus.Running | controller.Status == ServiceControllerStatus.Paused) + { + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 0, 15)); + } + } + + base.OnBeforeUninstall(savedState); + } + + /// + /// Raises the event. + /// + /// An that contains the state of the computer after all the installers contained in the property have completed their installations. + protected override void OnAfterInstall(IDictionary savedState) + { + try + { + using (var serviceController = new ServiceController(Program.ServiceName)) + { + serviceController.Start(); + } + } + catch (Exception e) + { + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Failed to start service {0} with exception {1}", Program.ServiceName, e)); + } + + base.OnAfterInstall(savedState); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.resx b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.resx new file mode 100644 index 0000000..601504d --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/ProjectInstaller.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 56 + + + 196, 17 + + + False + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DeleteService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DeleteService.cs new file mode 100644 index 0000000..e126c56 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DeleteService.cs @@ -0,0 +1,121 @@ +namespace Microsoft.InnerEye.Listener.Processor.Services +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common.Services; + + /// + /// Deletes incoming data that has been processed + /// + /// + public sealed class DeleteService : DequeueClientServiceBase + { + /// + /// Create a new IMessageQueue for the delete queue. + /// + /// new IMessageQueue. + public IMessageQueue DeleteQueue => DequeueMessageQueue; + + /// + /// Initializes a new instance of the class. + /// + /// The delete queue path. + /// Callback for dequeue service config. + /// The log. + public DeleteService( + string deleteQueuePath, + Func getDequeueServiceConfig, + ILogger logger) + : base(getDequeueServiceConfig, deleteQueuePath, logger, 1) // We currently limit the delete service to one instance + { + } + + /// + /// Called when [update tick] is called. This will wait for all work to execute then will pause for desired interval delay. + /// + /// The cancellation token. + /// + /// The async task. + /// + protected override async Task OnUpdateTickAsync(CancellationToken cancellationToken) + { + using (var transaction = CreateQueueTransaction()) + { + BeginMessageQueueTransaction(transaction); + + DeleteQueueItem deleteQueueItem = null; + + try + { + deleteQueueItem = await DequeueNextMessageAsync(transaction, cancellationToken); + + // Delete every path in the queue item. Each path could be a directory or a file. + foreach (var path in deleteQueueItem.Paths) + { + DeletePath(path); + } + + LogInformation(LogEntry.Create(AssociationStatus.Deleted, + deleteQueueItem: deleteQueueItem)); + + transaction.Commit(); + } + catch (MessageQueueReadException) + { + // We timed out trying to de-queue (no items on the queue). + // This exception doesn't need to be logged. + transaction.Abort(); + } + catch (OperationCanceledException) + { + // Throw operation canceled exceptions up to the worker thread. It will handle + // logging correctly. + transaction.Abort(); + throw; + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.DeleteError, + deleteQueueItem: deleteQueueItem), + e); + HandleExceptionForTransaction(deleteQueueItem, transaction); + } + } + } + + /// + /// Deletes the path (either a directory or file). + /// + /// The path to delete. + private void DeletePath(string path) + { + LogTrace(LogEntry.Create(AssociationStatus.DeletePath, path: path)); + + if (Directory.Exists(path)) + { + Directory.Delete(path, true); + } + else if (File.Exists(path)) + { + var file = new FileInfo(path); + file.Delete(); + + var directory = file.Directory; + + // If the directory is empty, lets delete the folder. + if (directory.GetFiles().Length == 0) + { + // Throw errors on delete - in-case we are still attempting to write to this folder. + directory.Delete(); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DownloadService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DownloadService.cs new file mode 100644 index 0000000..5390bea --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/DownloadService.cs @@ -0,0 +1,309 @@ +namespace Microsoft.InnerEye.Listener.Processor.Services +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common.Services; + + using Newtonsoft.Json; + + /// + /// The download service for downloading segmentation results. + /// + /// + public sealed class DownloadService : DequeueClientServiceBase + { + /// + /// The push queue path. + /// + private readonly string _pushQueuePath; + + /// + /// The delete queue path. + /// + private readonly string _deleteQueuePath; + + /// + /// Callback to get DownloadServiceConfig. + /// + private readonly Func _getDownloadServiceConfig; + + /// + /// The maximum time the service will wait for a result to finish. + /// + private DownloadServiceConfig _downloadServiceConfig; + + /// + /// Callback to create InnerEye segmentation client. + /// + private readonly Func _getInnerEyeSegmentationClient; + + /// + /// The InnerEye segmentation client. + /// + private IInnerEyeSegmentationClient _innerEyeSegmentationClient; + + /// + /// Create a new IMessageQueue for the delete queue. + /// + /// new IMessageQueue. + public IMessageQueue DeleteQueue => GatewayMessageQueue.Get(_deleteQueuePath); + + /// + /// Create a new IMessageQueue for the download queue. + /// + /// new IMessageQueue. + public IMessageQueue DownloadQueue => DequeueMessageQueue; + + /// + /// Create a new IMessageQueue for the push queue. + /// + /// new IMessageQueue. + public IMessageQueue PushQueue => GatewayMessageQueue.Get(_pushQueuePath); + + /// + /// Initializes a new instance of the class. + /// + /// Callback to create InnerEye segmentation client. + /// The download queue path. + /// The push queue path. + /// The delete queue path. + /// Callback for download service config. + /// Callback for dequeue service config. + /// The log. + /// The number of concurrent execution instances we should have. + public DownloadService( + Func getInnerEyeSegmentationClient, + string downloadQueuePath, + string pushQueuePath, + string deleteQueuePath, + Func getDownloadServiceConfig, + Func getDequeueServiceConfig, + ILogger logger, + int instances) + : base(getDequeueServiceConfig, downloadQueuePath, logger, instances) + { + _getInnerEyeSegmentationClient = getInnerEyeSegmentationClient ?? throw new ArgumentNullException(nameof(getInnerEyeSegmentationClient)); + _pushQueuePath = !string.IsNullOrWhiteSpace(pushQueuePath) ? pushQueuePath : throw new ArgumentException("The push queue path is null or white space.", nameof(pushQueuePath)); + _deleteQueuePath = !string.IsNullOrWhiteSpace(deleteQueuePath) ? deleteQueuePath : throw new ArgumentException("The delete queue path is null or white space.", nameof(deleteQueuePath)); + _getDownloadServiceConfig = getDownloadServiceConfig ?? throw new ArgumentNullException(nameof(getDownloadServiceConfig)); + } + + /// + protected override void OnServiceStart() + { + base.OnServiceStart(); + + _downloadServiceConfig = _getDownloadServiceConfig(); + + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = _getInnerEyeSegmentationClient.Invoke(); + } + + /// + /// Called when [update tick asynchronous]. + /// + /// The cancellation token. + /// The async task. + protected override async Task OnUpdateTickAsync(CancellationToken cancellationToken) + { + using (var transaction = CreateQueueTransaction()) + { + BeginMessageQueueTransaction(transaction); + + DownloadQueueItem queueItem = null; + + try + { + queueItem = await DequeueNextMessageAsync(transaction, cancellationToken); + + // Download the segmentation results + var segmentationResult = await GetSegmentationResultAsync( + queueItem, + _downloadServiceConfig.DownloadRetryTimespan, + _downloadServiceConfig.DownloadWaitTimeout, + cancellationToken); + + // The result can be null if the API encountered an error. This is logged by the above method. + if (segmentationResult != null) + { + // Downloaded the segmentation result. + LogInformation(LogEntry.Create(AssociationStatus.Downloaded, + downloadQueueItem: queueItem)); + + // Save the results to the result folder using the association Guid + await SaveDicomFilesAsync(queueItem.ResultsDirectory, segmentationResult); + + // Enqueue the segmentation result onto the push queue only when we are not in dry run mode + // and we have something to push + if (!queueItem.IsDryRun) + { + EnqueueMessage( + new PushQueueItem( + destinationApplicationEntity: queueItem.DestinationApplicationEntity, + calledApplicationEntityTitle: queueItem.CalledApplicationEntityTitle, + callingApplicationEntityTitle: queueItem.CallingApplicationEntityTitle, + associationGuid: queueItem.AssociationGuid, + associationDateTime: queueItem.AssociationDateTime, + filePaths: Directory.EnumerateFiles(queueItem.ResultsDirectory).ToArray()), + _pushQueuePath, + transaction); + } + } + + transaction.Commit(); + } + catch (MessageQueueReadException) + { + // We timed out trying to de-queue (no items on the queue). + // This exception doesn't need to be logged. + transaction.Abort(); + } + catch (OperationCanceledException) + { + // Throw operation canceled exceptions up to the worker thread. It will handle + // logging correctly. + transaction.Abort(); + throw; + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.DownloadError, downloadQueueItem: queueItem), + e); + + // If we can't process this item we may have data in the result directory; lets delete it. + HandleExceptionForTransaction( + queueItem: queueItem, + queueTransaction: transaction, + oldQueueItemAction: () => CleanUp(queueItem, transaction)); + } + } + } + + /// + /// The clean up action if the queue item has completed or failed and is now an old message. + /// + /// The queue item. + /// The message queue transaction. + private void CleanUp(DownloadQueueItem queueItem, IQueueTransaction transaction) + { + if (queueItem == null || transaction == null || string.IsNullOrWhiteSpace(queueItem.ResultsDirectory)) + { + return; + } + + EnqueueMessage( + new DeleteQueueItem(queueItem, queueItem.ResultsDirectory), + _deleteQueuePath, + transaction); + } + + /// + /// Opens a collection of Dicom file from a collection of binary arrays. + /// + /// The binary Dicom files. + /// The collection of Dicom file. + private static IEnumerable OpenDicomFiles(IEnumerable files) + { + foreach (var file in files) + { + using (var stream = new MemoryStream(file)) + { + yield return DicomFile.Open(stream); + } + } + } + + /// + /// Gets the segmentation result. + /// + /// The download queue item. + /// The delay between getting segmentation progress. + /// The maximum time we will wait for a segmentation result. + /// The cancellation token. + /// The segmentation result Dicom file or null. + private async Task GetSegmentationResultAsync(DownloadQueueItem downloadQueueItem, TimeSpan retryDelay, TimeSpan timeout, CancellationToken cancellationToken) + { + var referenceDicomFiles = OpenDicomFiles(downloadQueueItem.ReferenceDicomFiles); + + // Attempting to download result + LogInformation(LogEntry.Create(AssociationStatus.Downloading, + downloadQueueItem: downloadQueueItem, + downloadProgress: 0, + downloadError: string.Empty)); + + var tagReplacements = JsonConvert.DeserializeObject>(downloadQueueItem.TagReplacementJsonString); + + // Create a new token for the maximum time we will sit and wait for a result. + // Note: We need to check both the passed cancellation token and this new token for cancellation requests + using (var cancellationTokenSource = new CancellationTokenSource(timeout)) + { + while (!cancellationTokenSource.IsCancellationRequested && !cancellationToken.IsCancellationRequested) + { + var modelResult = await _innerEyeSegmentationClient.SegmentationResultAsync( + modelId: downloadQueueItem.ModelId, + segmentationId: downloadQueueItem.SegmentationID, + referenceDicomFiles: referenceDicomFiles, + userReplacements: tagReplacements); + + if (modelResult.Progress == 100 && modelResult.DicomResult != null) + { + return modelResult.DicomResult; + } + else if (!string.IsNullOrEmpty(modelResult.Error)) + { + LogError(LogEntry.Create(AssociationStatus.Downloading, + downloadQueueItem: downloadQueueItem, + downloadProgress: modelResult.Progress, + downloadError: modelResult.Error), + new Exception("Failed to get a segmentation result.")); + + // We cannot recover from this error, so we log and continue. + return null; + } + else + { + LogInformation(LogEntry.Create(AssociationStatus.Downloading, + downloadQueueItem: downloadQueueItem, + downloadProgress: modelResult.Progress, + downloadError: modelResult.Error)); + } + + // Make sure you pass the cancellation token, not the timeout token, so the service can stop timely + await Task.Delay(retryDelay, cancellationToken); + } + } + + return null; + } + + /// + /// Disposes of all managed resources. + /// + /// If we are disposing. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + { + return; + } + + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = null; + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/PushService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/PushService.cs new file mode 100644 index 0000000..a680499 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/PushService.cs @@ -0,0 +1,251 @@ +namespace Microsoft.InnerEye.Listener.Processor.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.InnerEye.Listener.Common.Providers; + using Microsoft.InnerEye.Listener.Common.Services; + using Microsoft.InnerEye.Listener.DataProvider.Interfaces; + using Microsoft.InnerEye.Listener.DataProvider.Models; + + /// + /// The push service. + /// + /// + public sealed class PushService : DequeueClientServiceBase + { + /// + /// The Dicom data sender. + /// + private readonly IDicomDataSender _dicomDataSender; + + /// + /// AET configuration provider. + /// + private readonly Func> _aetConfigProvider; + + /// + /// Cache AET configurations. + /// + private IEnumerable _aetConfigModels; + + /// + /// The delete queue path. + /// + private readonly string _deleteQueuePath; + + /// + /// Create a new IMessageQueue for the delete queue. + /// + /// new IMessageQueue. + public IMessageQueue DeleteQueue => GatewayMessageQueue.Get(_deleteQueuePath); + + /// + /// Create a new IMessageQueue for the push queue. + /// + /// new IMessageQueue. + public IMessageQueue PushQueue => DequeueMessageQueue; + + /// + /// Initializes a new instance of the class. + /// + /// AET configuration provider. + /// The Dicom data sender. + /// The push queue path. + /// The delete queue path. + /// Callback for dequeue service config. + /// The log. + /// The instances. + public PushService( + Func> aetConfigProvider, + IDicomDataSender dicomDataSender, + string pushQueuePath, + string deleteQueuePath, + Func getDequeueServiceConfig, + ILogger logger, + int instances) + : base(getDequeueServiceConfig, pushQueuePath, logger, instances) + { + _aetConfigProvider = aetConfigProvider ?? throw new ArgumentNullException(nameof(aetConfigProvider)); + _deleteQueuePath = !string.IsNullOrWhiteSpace(deleteQueuePath) ? deleteQueuePath : throw new ArgumentException("The Queue path should not be null or whitespace.", nameof(deleteQueuePath)); + _dicomDataSender = dicomDataSender ?? throw new ArgumentNullException(nameof(dicomDataSender)); + } + + /// + protected override void OnServiceStart() + { + base.OnServiceStart(); + + _aetConfigModels = _aetConfigProvider.Invoke(); + } + + /// + /// Called when [update tick] is called. This will wait for all work to execute then will pause for desired interval delay. + /// + /// The cancellation token. + /// + /// The async task. + /// + protected override async Task OnUpdateTickAsync(CancellationToken cancellationToken) + { + using (var transaction = CreateQueueTransaction()) + { + BeginMessageQueueTransaction(transaction); + + PushQueueItem queueItem = null; + + try + { + queueItem = await DequeueNextMessageAsync(transaction, cancellationToken); + + // If we have dequed this item more than once, lets check if the destination + // Dicom endpoint has been updated on the configuration service (or if the destination is null). + if (queueItem.DequeueCount > 1 || queueItem.DestinationApplicationEntity == null) + { + // Refresh the application entity config. + var applicationEntityConfig = ApplyAETModelConfigProvider.GetAETConfigs( + _aetConfigModels, + queueItem.CalledApplicationEntityTitle, + queueItem.CallingApplicationEntityTitle); + + if (applicationEntityConfig.Destination == null) + { + var exception = new ArgumentNullException("applicationEntityConfig.Destination", + "The result destination is null. The destination has not been configured."); + + LogError(LogEntry.Create(AssociationStatus.PushErrorDestinationEmpty, pushQueueItem: queueItem), + exception); + + throw exception; + } + + queueItem.DestinationApplicationEntity = new GatewayApplicationEntity( + title: applicationEntityConfig.Destination.Title, + port: applicationEntityConfig.Destination.Port, + ipAddress: applicationEntityConfig.Destination.Ip); + } + + if (queueItem.FilePaths.Any()) + { + var dicomFiles = ReadDicomFiles(queueItem.FilePaths, queueItem); + + await PushDicomFilesAsync( + pushQueueItem: queueItem, + ownApplicationEntityTitle: queueItem.CalledApplicationEntityTitle, + destination: queueItem.DestinationApplicationEntity, + cancellationToken: cancellationToken, + dicomFiles: dicomFiles.ToArray()); + } + + // Enqueue delete the files. + CleanUp(queueItem, transaction); + + transaction.Commit(); + } + catch (MessageQueueReadException) + { + // We timed out trying to de-queue (no items on the queue). + // This exception doesn't need to be logged. + transaction.Abort(); + } + catch (OperationCanceledException) + { + // Throw operation canceled exceptions up to the worker thread. It will handle + // logging correctly. + transaction.Abort(); + throw; + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.PushError, pushQueueItem: queueItem), + e); + + HandleExceptionForTransaction( + queueItem: queueItem, + queueTransaction: transaction, + oldQueueItemAction: () => CleanUp(queueItem, transaction)); + } + } + } + + /// + /// The clean up action if the queue item has completed or failed and is now an old message. + /// + /// The queue item. + /// The message queue transaction. + private void CleanUp(PushQueueItem queueItem, IQueueTransaction transaction) + { + if (queueItem == null || transaction == null) + { + return; + } + + EnqueueMessage( + new DeleteQueueItem(queueItem, queueItem.FilePaths), + _deleteQueuePath, + transaction); + } + + /// + /// Pushes the Dicom files to the destination application entity. + /// + /// The queue item. + /// Our own application entity tile. + /// The destination. + /// The cancellation token. + /// The Dicom files to push. + /// The async task. + private async Task PushDicomFilesAsync( + PushQueueItem pushQueueItem, + string ownApplicationEntityTitle, + GatewayApplicationEntity destination, + CancellationToken cancellationToken, + params DicomFile[] dicomFiles) + { + if (cancellationToken.IsCancellationRequested) + { + throw new TaskCanceledException(); + } + + if (dicomFiles.Length == 0) + { + LogError(LogEntry.Create(AssociationStatus.PushErrorFilesEmpty, pushQueueItem: pushQueueItem), + new Exception("No files to push.")); + return; + } + + LogInformation(LogEntry.Create(AssociationStatus.Pushing, + pushQueueItem: pushQueueItem, + destination: GetDestination(destination))); + + var result = await _dicomDataSender.SendFilesAsync( + ownApplicationEntityTitle, + destination.Title, + destination.Port, + destination.IpAddress, + dicomFiles); + + if (result.Any(x => x.Item2 != DicomOperationResult.Success)) + { + throw new ArgumentException("Failed to push"); + } + + LogInformation(LogEntry.Create(AssociationStatus.Pushed, + pushQueueItem: pushQueueItem, + destination: GetDestination(destination))); + } + + private static (string ipAddress, string title, int port) GetDestination(GatewayApplicationEntity gatewayApplicationEntity) => + (gatewayApplicationEntity.IpAddress, gatewayApplicationEntity.Title, gatewayApplicationEntity.Port); + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/UploadService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/UploadService.cs new file mode 100644 index 0000000..7bd0625 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/Services/UploadService.cs @@ -0,0 +1,475 @@ +namespace Microsoft.InnerEye.Listener.Processor.Services +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.InnerEye.Listener.Common.Providers; + using Microsoft.InnerEye.Listener.Common.Services; + using Microsoft.InnerEye.Listener.DataProvider.Models; + + using Newtonsoft.Json; + + /// + /// Service for picking items of the message queue and sends data to the InnerEye cloud segmentation service. + /// + /// + public sealed class UploadService : DequeueClientServiceBase + { + /// + /// The results folder. + /// + private const string ResultsFolder = "Results"; + + /// + /// The download queue path. + /// + private readonly string _downloadQueuePath; + + /// + /// The delete queue path. + /// + private readonly string _deleteQueuePath; + + /// + /// Callback to create InnerEye segmentation client. + /// + private readonly Func _getInnerEyeSegmentationClient; + + /// + /// The InnerEye segmentation client. + /// + private IInnerEyeSegmentationClient _innerEyeSegmentationClient; + + /// + /// AET configuration provider. + /// + private readonly Func> _aetConfigProvider; + + /// + /// Cache AET configurations. + /// + private IEnumerable _aetConfigModels; + + /// + /// Create a new IMessageQueue for the delete queue. + /// + /// new IMessageQueue. + public IMessageQueue DeleteQueue => GatewayMessageQueue.Get(_deleteQueuePath); + + /// + /// Create a new IMessageQueue for the download queue. + /// + /// new IMessageQueue. + public IMessageQueue DownloadQueue => GatewayMessageQueue.Get(_downloadQueuePath); + + /// + /// Create a new IMessageQueue for the upload queue. + /// + /// new IMessageQueue. + public IMessageQueue UploadQueue => DequeueMessageQueue; + + /// + /// Initializes a new instance of the class. + /// + /// Callback to create InnerEye segmentation client. + /// AET configuration provider. + /// The upload queue path. + /// The download queue path. + /// The delete queue path. + /// Callback for dequeue service config. + /// The log. + /// The number of concurrent execution instances we should have. + public UploadService( + Func getInnerEyeSegmentationClient, + Func> aetConfigProvider, + string uploadQueuePath, + string downloadQueuePath, + string deleteQueuePath, + Func getDequeueServiceConfig, + ILogger logger, + int instances) + : base(getDequeueServiceConfig, uploadQueuePath, logger, instances) + { + _getInnerEyeSegmentationClient = getInnerEyeSegmentationClient ?? throw new ArgumentNullException(nameof(getInnerEyeSegmentationClient)); + _aetConfigProvider = aetConfigProvider ?? throw new ArgumentNullException(nameof(aetConfigProvider)); + _downloadQueuePath = !string.IsNullOrWhiteSpace(downloadQueuePath) ? downloadQueuePath : throw new ArgumentException("The download path should not be null or whitespace.", nameof(downloadQueuePath)); + _deleteQueuePath = !string.IsNullOrWhiteSpace(deleteQueuePath) ? deleteQueuePath : throw new ArgumentException("The delete path should not be null or whitespace.", nameof(deleteQueuePath)); + } + + /// + protected override void OnServiceStart() + { + base.OnServiceStart(); + + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = _getInnerEyeSegmentationClient.Invoke(); + + _aetConfigModels = _aetConfigProvider.Invoke(); + } + + /// + /// Called when [update tick] is called. This will wait for all work to execute then will pause for desired interval delay. + /// + /// The cancellation token. + /// + /// The async task. + /// + protected override async Task OnUpdateTickAsync(CancellationToken cancellationToken) + { + using (var transaction = CreateQueueTransaction()) + { + BeginMessageQueueTransaction(transaction); + + UploadQueueItem queueItem = null; + + try + { + queueItem = await DequeueNextMessageAsync(transaction, cancellationToken); + + // If the directory does not exist we cannot process this queue item. + // Lets log but remove this queue item. + if (Directory.Exists(queueItem.AssociationFolderPath)) + { + await ProcessUploadQueueItem(queueItem, transaction); + + // Enqueue the message to delete the association folder + CleanUp(queueItem, transaction); + } + else + { + LogError(LogEntry.Create(AssociationStatus.UploadErrorAssocationFolderDeleted, uploadQueueItem: queueItem), + new Exception("The association folder has been deleted.")); + } + + transaction.Commit(); + } + catch (MessageQueueReadException) + { + // We timed out trying to de-queue (no items on the queue). + // This exception doesn't need to be logged. + transaction.Abort(); + } + catch (OperationCanceledException) + { + // Throw operation canceled exceptions up to the worker thread. It will handle + // logging correctly. + transaction.Abort(); + throw; + } + catch (Exception e) + { + LogError(LogEntry.Create(AssociationStatus.UploadError, uploadQueueItem: queueItem), + e); + + HandleExceptionForTransaction( + queueItem: queueItem, + queueTransaction: transaction, + oldQueueItemAction: () => CleanUp(queueItem, transaction)); + } + } + } + + /// + /// The clean up action if the queue item has completed or failed and is now an old message. + /// + /// The queue item. + /// The message queue transaction. + private void CleanUp(UploadQueueItem queueItem, IQueueTransaction transaction) + { + if (queueItem == null || transaction == null) + { + return; + } + + // During a clean-up with remove everything in the association folder. + EnqueueMessage( + new DeleteQueueItem(queueItem, queueItem.AssociationFolderPath), + _deleteQueuePath, + transaction); + } + + /// + /// Uploads the files. + /// + /// The dicom files. + /// The upload queue item. + /// The queue transaction. + /// The async task. + private async Task ProcessUploadQueueItem(UploadQueueItem uploadQueueItem, IQueueTransaction queueTransaction) + { + var clientConfiguration = ApplyAETModelConfigProvider.GetAETConfigs( + _aetConfigModels, + uploadQueueItem.CalledApplicationEntityTitle, + uploadQueueItem.CallingApplicationEntityTitle); + + LogTrace(LogEntry.Create(AssociationStatus.UploadProcessQueueItem)); + + switch (clientConfiguration.Config.AETConfigType) + { + // ML Model or ML Model with Dry Run Result + case AETConfigType.Model: + case AETConfigType.ModelWithResultDryRun: + // Load all DICOM files in the received folder. + var dicomFiles = ReadDicomFiles(uploadQueueItem.AssociationFolderPath, uploadQueueItem); + await ProcessModelConfig(dicomFiles, uploadQueueItem, queueTransaction, clientConfiguration); + + break; + // ML Model dry run + case AETConfigType.ModelDryRun: + // Anonymize and save the files locally for the dry run using the segmentation anonymisation protocol + await AnonymiseAndSaveDicomFilesAsync( + anonymisationProtocolId: _innerEyeSegmentationClient.SegmentationAnonymisationProtocolId, + anonymisationProtocol: _innerEyeSegmentationClient.SegmentationAnonymisationProtocol, + uploadQueueItem: uploadQueueItem, + aETConfigType: clientConfiguration.Config.AETConfigType); + + break; + } + } + + /// + /// Anonymises every DICOM file in the specified folder path and saves locally to the + /// correct dry run folder. + /// + /// The anonymisation protocol unique identifier. + /// The anonymisation protocol. + /// The upload queue item. + /// Type of application entity title configuration. + /// The async task. + private async Task AnonymiseAndSaveDicomFilesAsync( + Guid anonymisationProtocolId, + IEnumerable anonymisationProtocol, + UploadQueueItem uploadQueueItem, + AETConfigType aETConfigType) + { + var dryRunFolder = DryRunFolders.GetFolder(aETConfigType); + var resultFolderPath = Path.Combine(uploadQueueItem.RootDicomFolderPath, dryRunFolder, uploadQueueItem.AssociationGuid.ToString()); + + foreach (var filePath in EnumerateFiles(uploadQueueItem.AssociationFolderPath, uploadQueueItem)) + { + var dicomFile = TryOpenDicomFile(filePath, uploadQueueItem); + + // Not a DICOM file or is not a structure set file. + if (dicomFile == null) + { + EnqueueMessage(new DeleteQueueItem(uploadQueueItem, filePath), _deleteQueuePath); + continue; + } + + var anonymizedDicomFile = _innerEyeSegmentationClient.AnonymizeDicomFile( + dicomFile: dicomFile, + anonymisationProtocolId: anonymisationProtocolId, + anonymisationProtocol: anonymisationProtocol); + + await SaveDicomFilesAsync(resultFolderPath, anonymizedDicomFile); + + // This item has been saved, we can now delete this file + EnqueueMessage(new DeleteQueueItem(uploadQueueItem, filePath), _deleteQueuePath); + } + } + + /// + /// Processes the model configuration. + /// + /// The upload queue item. + /// The dicom files. + /// The queue transaction. + /// The client configuration. + /// The waitable task. + private async Task ProcessModelConfig( + IEnumerable dicomFiles, + UploadQueueItem uploadQueueItem, + IQueueTransaction queueTransaction, + ClientAETConfig clientConfiguration) + { + var modelMatchResult = ApplyAETModelConfigProvider.ApplyAETModelConfig(clientConfiguration.Config.ModelsConfig, dicomFiles); + + if (modelMatchResult.Matched) + { + var model = modelMatchResult.Result; + var queueItem = await StartSegmentationAsync(model.ChannelData, uploadQueueItem, model.ModelId, model.TagReplacements.ToArray(), clientConfiguration); + + EnqueueMessage(queueItem, _downloadQueuePath, queueTransaction); + } + else + { + var failedDicomTags = modelMatchResult.GetDicomConstraintsDicomTags(); + + // Log all the tags that did not match + LogError(LogEntry.Create(AssociationStatus.UploadErrorTagsDoNotMatch, + uploadQueueItem: uploadQueueItem, + failedDicomTags: string.Join(",", failedDicomTags.Select(x => x.DictionaryEntry.Name))), + new Exception("Failed to find a model for the received Dicom data.")); + } + } + + /// + /// Gets the directory to store results from the download service. + /// + /// The root Dicom folder. + /// If we should save the data to a dry run folder or results folder. + /// The results directory. + private static string GetResultsDirectory(string rootDicomFolder, bool isDryRun) + { + return Directory.CreateDirectory( + Path.Combine( + rootDicomFolder, + isDryRun ? DryRunFolders.DryRunModelWithResultFolder : ResultsFolder, + Guid.NewGuid().ToString())).FullName; + } + + /// + /// Copies all the data needed to be sent in the result association to the result directory. + /// + /// The result directory. + /// The machine learning model channel data. + /// The collection of file paths. + private static void CopySendDataToResultsDirectory(string resultsDirectory, IEnumerable channelData) + { + foreach (var channel in channelData) + { + foreach (var dicomFile in channel.DicomFiles) + { + dicomFile.Save(Path.Combine(resultsDirectory, string.Format(CultureInfo.InvariantCulture, "{0}.dcm", Guid.NewGuid()))); + } + } + } + + /// + /// Starts the segmentation task. + /// + /// The channel data. + /// The upload queue item. + /// The model unique identifier. + /// The tag replacements. + /// The client configuration. + /// The queue item. + /// If the result destination is not a valid AET. + private async Task StartSegmentationAsync + (IEnumerable channelData, + UploadQueueItem uploadQueueItem, + string modelGuid, + TagReplacement[] tagReplacements, + ClientAETConfig clientConfiguration) + { + if (clientConfiguration.Destination == null) + { + var exception = new ArgumentNullException("clientConfiguration.Destination", + "The result destination is null. The destination has not been configured."); + + LogError(LogEntry.Create(AssociationStatus.UploadErrorDestinationEmpty, uploadQueueItem: uploadQueueItem), + exception); + + throw exception; + } + + // Validate the destination before uploading. + ValidateDicomEndpoint(uploadQueueItem, clientConfiguration.Destination); + + LogInformation(LogEntry.Create(AssociationStatus.Uploading, + uploadQueueItem: uploadQueueItem, + modelId: modelGuid)); + + var referenceDicomFiles = channelData.First().DicomFiles.ToArray(); + + // Read all the bytes from the reference Dicom file and create new DICOM files with only the required DICOM tags + var referenceDicomByteArrays = referenceDicomFiles + .CreateNewDicomFileWithoutPixelData( + _innerEyeSegmentationClient.SegmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + // Start the segmentation + var (segmentationId, postedImages) = await _innerEyeSegmentationClient.StartSegmentationAsync(modelGuid, channelData); + + LogInformation(LogEntry.Create(AssociationStatus.Uploaded, + uploadQueueItem: uploadQueueItem, + segmentationId: segmentationId, + modelId: modelGuid)); + + var isDryRun = clientConfiguration.Config.AETConfigType == AETConfigType.ModelWithResultDryRun; + var resultsDirectory = GetResultsDirectory( + uploadQueueItem.RootDicomFolderPath, + isDryRun: isDryRun); + + // Copy any data needed to be sent with result to results directory if needed. + if (clientConfiguration.ShouldReturnImage) + { + CopySendDataToResultsDirectory(resultsDirectory, channelData); + } + + return new DownloadQueueItem( + segmentationId: segmentationId, + modelId: modelGuid, + resultsDirectory: resultsDirectory, + referenceDicomFiles: referenceDicomByteArrays, + calledApplicationEntityTitle: uploadQueueItem.CalledApplicationEntityTitle, + callingApplicationEntityTitle: uploadQueueItem.CallingApplicationEntityTitle, + destinationApplicationEntity: new GatewayApplicationEntity( + clientConfiguration.Destination.Title, + clientConfiguration.Destination.Port, + clientConfiguration.Destination.Ip), + tagReplacementJsonString: JsonConvert.SerializeObject(tagReplacements), + associationGuid: uploadQueueItem.AssociationGuid, + associationDateTime: uploadQueueItem.AssociationDateTime, + isDryRun: isDryRun); + } + + /// + /// Validates the DICOM endpoint. + /// + /// Upload queue item. + /// The DICOM endpoint to validate. + private void ValidateDicomEndpoint(UploadQueueItem uploadQueueItem, DicomEndPoint dicomEndPoint) + { + var titleValidationResult = ApplicationEntityValidationHelpers.ValidateTitle(dicomEndPoint.Title); + var portValidationResult = ApplicationEntityValidationHelpers.ValidatePort(dicomEndPoint.Port); + var ipValidationResult = ApplicationEntityValidationHelpers.ValidateIPAddress(dicomEndPoint.Ip); + + var result = titleValidationResult && portValidationResult && ipValidationResult; + + // Validate the destination before uploading. + if (!result) + { + var exception = new ArgumentException("The DICOM endpoint is not valid.", nameof(dicomEndPoint)); + + LogError(LogEntry.Create(AssociationStatus.UploadErrorDicomEndpointInvalid, + uploadQueueItem: uploadQueueItem, + destination: (ipAddress: dicomEndPoint.Ip, title: dicomEndPoint.Title, port: dicomEndPoint.Port)), + exception); + + // We cannot process message + throw exception; + } + } + + /// + /// Disposes of all managed resources. + /// + /// If we are disposing. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + { + return; + } + + _innerEyeSegmentationClient?.Dispose(); + _innerEyeSegmentationClient = null; + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/log4net.config b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/log4net.config new file mode 100644 index 0000000..f285be6 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Processor/log4net.config @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Microsoft.InnerEye.Listener.Receiver.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Microsoft.InnerEye.Listener.Receiver.csproj new file mode 100644 index 0000000..05a7046 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Microsoft.InnerEye.Listener.Receiver.csproj @@ -0,0 +1,46 @@ + + + Exe + net462 + x64 + Microsoft.InnerEye.Listener.Receiver + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Receiver Windows Service for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + Always + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Program.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Program.cs new file mode 100644 index 0000000..9ef9483 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Program.cs @@ -0,0 +1,51 @@ +namespace Microsoft.InnerEye.Listener.Receiver +{ + using Common.Services; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Listener.Common.Providers; + using Services; + + public static class Program + { + /// + /// The service name. + /// + public const string ServiceName = ServiceNames.ReceiveServiceName; + + /// + /// The main entry point for the application. + /// + public static void Main() + { + var configurationsPathRoot = "../../../../../SampleConfigurations"; + + // Create the loggerFactory as Console + Log4Net. + using (var loggerFactory = LoggerFactory.Create(builder => + { + builder.AddConsole(); + builder.SetMinimumLevel(LogLevel.Trace); + builder.AddLog4Net(); + })) + { + var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider( + loggerFactory.CreateLogger("ProcessorSettings"), + configurationsPathRoot); + + // The ProjectInstaller.cs uses the service name to install the service. + // If you change it please update the ProjectInstaller.cs + ServiceHelpers.RunServices( + ServiceName, + gatewayReceiveConfigProvider.ServiceSettings(), + new ConfigurationService( + null, + gatewayReceiveConfigProvider.ConfigurationServiceConfig, + loggerFactory.CreateLogger("ConfigurationService"), + new ReceiveService( + gatewayReceiveConfigProvider.ReceiveServiceConfig, + GatewayMessageQueue.UploadQueuePath, + loggerFactory.CreateLogger("ReceiveService")))); + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.Designer.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.Designer.cs new file mode 100644 index 0000000..31ddc5b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.Designer.cs @@ -0,0 +1,60 @@ +namespace Microsoft.InnerEye.Listener.Receiver +{ + partial class ProjectInstaller + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.ReceiveServiceProcessInstaller = new System.ServiceProcess.ServiceProcessInstaller(); + this.ReceiverInstaller = new System.ServiceProcess.ServiceInstaller(); + // + // ReceiveServiceProcessInstaller + // + this.ReceiveServiceProcessInstaller.Account = System.ServiceProcess.ServiceAccount.LocalSystem; + this.ReceiveServiceProcessInstaller.Password = null; + this.ReceiveServiceProcessInstaller.Username = null; + // + // ReceiverInstaller + // + this.ReceiverInstaller.DisplayName = "InnerEye Gateway Receive Service"; + this.ReceiverInstaller.ServiceName = Program.ServiceName; + this.ReceiverInstaller.StartType = System.ServiceProcess.ServiceStartMode.Automatic; + this.ReceiverInstaller.DelayedAutoStart = true; + // + // ProjectInstaller + // + this.Installers.AddRange(new System.Configuration.Install.Installer[] { + this.ReceiveServiceProcessInstaller, + this.ReceiverInstaller}); + + } + + #endregion + + private System.ServiceProcess.ServiceProcessInstaller ReceiveServiceProcessInstaller; + private System.ServiceProcess.ServiceInstaller ReceiverInstaller; + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.cs new file mode 100644 index 0000000..200af87 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.cs @@ -0,0 +1,65 @@ +namespace Microsoft.InnerEye.Listener.Receiver +{ + using System; + using System.Collections; + using System.ComponentModel; + using System.Configuration.Install; + using System.Diagnostics; + using System.Globalization; + using System.ServiceProcess; + + /// + /// The project installer class. + /// + /// + [RunInstaller(true)] + public partial class ProjectInstaller : Installer + { + /// + /// Initializes a new instance of the class. + /// + public ProjectInstaller() + { + InitializeComponent(); + } + + /// + /// Raises the event. + /// + /// An that contains the state of the computer before the installers in the property uninstall their installations. + protected override void OnBeforeUninstall(IDictionary savedState) + { + using (var controller = new ServiceController(Program.ServiceName)) + { + if (controller.Status == ServiceControllerStatus.Running | controller.Status == ServiceControllerStatus.Paused) + { + controller.Stop(); + controller.WaitForStatus(ServiceControllerStatus.Stopped, new TimeSpan(0, 0, 0, 15)); + } + } + + base.OnBeforeUninstall(savedState); + } + + /// + /// Raises the event. + /// + /// An that contains the state of the computer after all the installers contained in the property have completed their installations. + protected override void OnAfterInstall(IDictionary savedState) + { + try + { + using (var serviceController = new ServiceController(Program.ServiceName)) + { + serviceController.Start(); + } + } + catch (Exception e) + { + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Failed to start service {0} with exception {1}", Program.ServiceName, e)); + } + + base.OnAfterInstall(savedState); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.resx b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.resx new file mode 100644 index 0000000..a75408a --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/ProjectInstaller.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 95 + + + 17, 56 + + + False + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Services/ReceiveService.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Services/ReceiveService.cs new file mode 100644 index 0000000..61ec190 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/Services/ReceiveService.cs @@ -0,0 +1,296 @@ +namespace Microsoft.InnerEye.Listener.Receiver.Services +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + using Dicom.Network; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Gateway.Logging; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common.Services; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.DataProvider.Interfaces; + using Microsoft.InnerEye.Listener.DataProvider.Models; + + /// + /// The receive service. + /// + public sealed class ReceiveService : ThreadedServiceBase + { + /// + /// The upload queue path. + /// + private readonly string _uploadQueuePath; + + /// + /// Receiver configuration callback. + /// + private readonly Func _getReceiveServiceConfig; + + /// + /// The data receiver for receiving data over Dicom. + /// + private IDicomDataReceiver _dataReceiver; + + /// + /// A cached reference of the gateway config. + /// + private ReceiveServiceConfig _receiveServiceConfig; + + /// + /// Create a new IMessageQueue for the upload queue. + /// + /// new IMessageQueue. + public IMessageQueue UploadQueue => GatewayMessageQueue.Get(_uploadQueuePath); + + /// + /// Initializes a new instance of the class. + /// + /// Callback to get configuration. + /// The upload queue path. + /// The log. + public ReceiveService( + Func getReceiveServiceConfig, + string uploadQueuePath, + ILogger logger) + : base(logger, 1) + { + _uploadQueuePath = !string.IsNullOrWhiteSpace(uploadQueuePath) ? uploadQueuePath : throw new ArgumentException("The upload queue path is null or white space.", nameof(uploadQueuePath)); + _getReceiveServiceConfig = getReceiveServiceConfig ?? throw new ArgumentNullException(nameof(getReceiveServiceConfig)); + } + + /// + /// Called when the service is started. + /// + protected override void OnServiceStart() + { + LogTrace(LogEntry.Create(AssociationStatus.ReceiveServiceStart)); + + _receiveServiceConfig = _getReceiveServiceConfig(); + + // Create a folder for saving data and the data saver object. + var imageSaver = new ListenerDicomSaver(_receiveServiceConfig.RootDicomFolder); + + _dataReceiver = new ListenerDataReceiver(imageSaver); + _dataReceiver.DataReceived += DataReceiver_DataReceived; + + // Start listening + var serverStarted = _dataReceiver.StartServer( + port: _receiveServiceConfig.GatewayDicomEndPoint.Port, + getAcceptedTransferSyntaxes: GetAcceptedSopClassesAndTransferSyntaxes, + timeout: TimeSpan.FromSeconds(2)); + + if (!serverStarted) + { + throw new ArgumentException("Failed to start the Dicom data receiver. The input configuration is not correct.", nameof(_receiveServiceConfig)); + } + } + + /// + /// Called when [service stop]. + /// + protected override void OnServiceStop() + { + if (_dataReceiver == null) + { + return; + } + + _dataReceiver.DataReceived -= DataReceiver_DataReceived; + _dataReceiver.StopServer(); + _dataReceiver.Dispose(); + _dataReceiver = null; + } + + /// + /// Called when [update tick] is called. This will wait for all work to execute then will pause for desired interval delay. + /// + /// The cancellation token. + /// + /// The async task. + /// + protected override Task OnUpdateTickAsync(CancellationToken cancellationToken) + { + return Task.Delay(TimeSpan.FromMinutes(60), cancellationToken); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + base.Dispose(true); + + if (!disposing) + { + return; + } + + DisposeDataReceiver(); + } + + /// + /// Method for when data is received. Used for adding a received folder onto the message queue + /// when the association is closed. + /// + /// The sender. + /// The progress update. + private void DataReceiver_DataReceived(object sender, DicomDataReceiverProgressEventArgs e) + { + var queueItem = new UploadQueueItem( + calledApplicationEntityTitle: e.DicomAssociation?.CalledAE, + callingApplicationEntityTitle: e.DicomAssociation?.CallingAE, + associationFolderPath: e.FolderPath, + rootDicomFolderPath: e.RootFolderPath, + associationGuid: e.AssociationId, + associationDateTime: e.SocketConnectionDateTime); + + if (e.ProgressCode == DicomReceiveProgressCode.AssociationReleased || e.ProgressCode == DicomReceiveProgressCode.TransferAborted) + { + // Send a log event + LogInformation(LogEntry.Create(AssociationStatus.DicomAssociationClosed, + uploadQueueItem: queueItem, + dicomDataReceiverProgress: CreateReceiveProperties(e))); + + // If no data has been received, we do not need to add to the message queue. + // An example of a no data received scenario is a Dicom echo. + if (!e.AnyDataReceived) + { + return; + } + + using (var transaction = CreateQueueTransaction(_uploadQueuePath)) + { + BeginMessageQueueTransaction(transaction); + + try + { + // Add the receive queue item onto the queue. + // No retry logic as if the method fails, retrying will not help + EnqueueMessage(queueItem, _uploadQueuePath, transaction); + transaction.Commit(); + } + // This should never happen unless someone has manually changed the queue configuration + catch (Exception exception) + { + transaction.Abort(); + LogError(LogEntry.Create(AssociationStatus.ReceiveEnqueueError, uploadQueueItem: queueItem), + exception); + } + } + } + else if (e.ProgressCode == DicomReceiveProgressCode.FileReceived + || e.ProgressCode == DicomReceiveProgressCode.ConnectionClosed + || e.ProgressCode == DicomReceiveProgressCode.AssociationEstablished) + { + LogInformation(LogEntry.Create(AssociationStatus.FileReceived, + uploadQueueItem: queueItem, + dicomDataReceiverProgress: CreateReceiveProperties(e))); + } + else if (e.ProgressCode == DicomReceiveProgressCode.AssociationEstablished) + { + LogInformation(LogEntry.Create(AssociationStatus.DicomAssociationOpened, + uploadQueueItem: queueItem, + dicomDataReceiverProgress: CreateReceiveProperties(e))); + } + else if (e.ProgressCode == DicomReceiveProgressCode.Echo) + { + LogInformation(LogEntry.Create(AssociationStatus.DicomEcho, + uploadQueueItem: queueItem, + dicomDataReceiverProgress: CreateReceiveProperties(e))); + } + else + { + LogError(LogEntry.Create(AssociationStatus.ReceiveUploadError, uploadQueueItem: queueItem, + dicomDataReceiverProgress: CreateReceiveProperties(e)), + new Exception("Cannot add to upload queue")); + } + } + + /// + /// Gets the accepted SOP classes and transfer syntaxes. + /// + /// The accepted SOP classes and transfer syntaxes. + private IReadOnlyDictionary GetAcceptedSopClassesAndTransferSyntaxes() + { + try + { + var gatewayReceiveConfig = _getReceiveServiceConfig(); + + if (gatewayReceiveConfig != null) + { + _receiveServiceConfig = gatewayReceiveConfig; + } + } + // We catch all exceptions and return the latest cached result on error. We do not want to stop accepting data + // just because we cannot communicate with our API. We should stop processing further down the chain. + catch (Exception e) + { + LogError(LogEntry.Create(ServiceStatus.GetAcceptedSopClassesAndTransferSyntaxesError), + e); + } + + return _receiveServiceConfig.AcceptedSopClassesAndTransferSyntaxes; + } + + /// + /// Disposes of the data receiver and sets the private value to null. + /// + private void DisposeDataReceiver() + { + if (_dataReceiver == null) + { + return; + } + + _dataReceiver.DataReceived -= DataReceiver_DataReceived; + + _dataReceiver.Dispose(); + _dataReceiver = null; + } + + /// + /// Creates the receive properties for logging. + /// + /// The instance containing the event data. + /// The receive properties. + private static (object progressCode, string remoteHost, int remotePort, string uid, string version, string logPresentation) CreateReceiveProperties(DicomDataReceiverProgressEventArgs e) => + (e.ProgressCode, e.DicomAssociation.RemoteHost, e.DicomAssociation.RemotePort, + e.DicomAssociation.RemoteImplementationClassUID?.UID, e.DicomAssociation.RemoteImplementationVersion, + // This is causing too verbose logging. Consider turning back on if running a telemetry/logging solution that can deal with it + //PresentationContextsToLogString(e.DicomAssociation.PresentationContexts) + null); + + + /// + /// Converts a Dicom presentation context collection to a human readable string. + /// + /// The presentation context collection. + /// The human readable string. + [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Could be used for detailed logging.")] + private static string PresentationContextsToLogString(DicomPresentationContextCollection presentationContextCollection) + { + var stringBuilder = new StringBuilder(); + + foreach (var item in presentationContextCollection) + { + stringBuilder.Append( + string.Format( + CultureInfo.InvariantCulture, + "'{0}:{1}' ", + item.AbstractSyntax.Name, + item.AcceptedTransferSyntax?.UID.Name)); + } + + return stringBuilder.ToString(); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/log4net.config b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/log4net.config new file mode 100644 index 0000000..a053c1e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Receiver/log4net.config @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCP.cfg b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCP.cfg new file mode 100644 index 0000000..cff235a --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCP.cfg @@ -0,0 +1,78 @@ +# +# Purpose: Defines transfer syntaxes for dcmtk store scp for testing. + +# ============================================================================ +[[TransferSyntaxes]] +# ============================================================================ + +[LEImplicitOnly] +TransferSyntax1 = LittleEndianImplicit + +[LEExplicitOnly] +TransferSyntax1 = LittleEndianExplicit + +[BEExplicitOnly] +TransferSyntax1 = BigEndianExplicit + +[Uncompressed] +TransferSyntax1 = LittleEndianExplicit +TransferSyntax2 = BigEndianExplicit +TransferSyntax3 = LittleEndianImplicit + +[Unsupported] +TransferSyntax1 = RLELossless + +# ============================================================================ +[[PresentationContexts]] +# ============================================================================ + +[RadStandardPCs] +PresentationContext1 = CTImageStorage\Uncompressed +PresentationContext2 = MRImageStorage\Uncompressed +PresentationContext3 = RTStructureSetStorage\LEImplicitOnly +PresentationContext4 = VerificationSOPClass\Uncompressed + +[RadUnsupportedPCs] +PresentationContext1 = CTImageStorage\Unsupported +PresentationContext2 = MRImageStorage\Unsupported +PresentationContext3 = RTStructureSetStorage\Unsupported + +[RadPartiallySupportedPCs] +PresentationContext1 = CTImageStorage\Uncompressed +PresentationContext2 = MRImageStorage\Unsupported +PresentationContext3 = RTStructureSetStorage\Unsupported + +[RadCTLEEPC] +PresentationContext1 = CTImageStorage\LEExplicitOnly +PresentationContext2 = RTStructureSetStorage\LEImplicitOnly + +[RadCTLEIPC] +PresentationContext1 = CTImageStorage\LEImplicitOnly +PresentationContext2 = RTStructureSetStorage\LEImplicitOnly + +[RadCTBEEPC] +PresentationContext1 = CTImageStorage\BEExplicitOnly +PresentationContext2 = RTStructureSetStorage\LEImplicitOnly + + +# ============================================================================ +[[Profiles]] +# ============================================================================ + +[RadStandardProfile] +PresentationContexts = RadStandardPCs + +[UnsupportedProfile] +PresentationContexts = RadUnsupportedPCs + +[RadPartiallySupportedProfile] +PresentationContexts = RadPartiallySupportedPCs + +[RadCTLEEProfile] +PresentationContexts = RadCTLEEPC + +[RadCTLEIProfile] +PresentationContexts = RadCTLEIPC + +[RadCTBEEProfile] +PresentationContexts = RadCTBEEPC \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCU.cfg b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCU.cfg new file mode 100644 index 0000000..5c0c602 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Assets/SCU.cfg @@ -0,0 +1,198 @@ +# +# Purpose: Defines transfer syntaxes for dcmtk store scu for testing. + +# ============================================================================ +[[TransferSyntaxes]] +# ============================================================================ + +[ImplicitLE] +TransferSyntax1 = LittleEndianImplicit + +[ExplicitLE] +TransferSyntax1 = LittleEndianExplicit + +[ExplicitBE] +TransferSyntax1 = BigEndianExplicit + +[RLE] +TransferSyntax1 = RLELossless + +[JPEGLosslessNonHierarchical14] +TransferSyntax1 = 1.2.840.10008.1.2.4.70 + +[JPEGLossless] +TransferSyntax1 = 1.2.840.10008.1.2.4.57 + +[JPEGLSLossless] +TransferSyntax1 = 1.2.840.10008.1.2.4.80 + +[MixedStandardTS] +TransferSyntax1 = LittleEndianImplicit +TransferSyntax2 = LittleEndianExplicit +TransferSyntax3 = BigEndianExplicit + +[AllSupportedTS] +TransferSyntax1 = LittleEndianImplicit +TransferSyntax2 = LittleEndianExplicit +TransferSyntax3 = BigEndianExplicit +TransferSyntax4 = RLELossless +TransferSyntax5 = 1.2.840.10008.1.2.4.70 +TransferSyntax6 = 1.2.840.10008.1.2.4.57 +TransferSyntax7 = 1.2.840.10008.1.2.4.80 + +# ============================================================================ +# jpeg2000 lossless +[Unsupported1] +TransferSyntax1 = 1.2.840.10008.1.2.4.90 + +# Deflate fo - dicom doesn't like it. +[Unsupported2] +TransferSyntax1 = DeflatedLittleEndianExplicit + +[Unsupported3] +TransferSyntax1 = 1.2.840.10008.1.2.4.91 + + +# ============================================================================ +[[PresentationContexts]] +# ============================================================================ + +[CTImplicitLE] +PresentationContext1 = CTImageStorage\ImplicitLE + +[CTExplicitLE] +PresentationContext1 = CTImageStorage\ExplicitLE + +[CTExplicitBE] +PresentationContext1 = CTImageStorage\ExplicitBE + +[CTRLE] +PresentationContext1 = CTImageStorage\RLE + +[CTJPEGLosslessNonHierarchical14] +PresentationContext1 = CTImageStorage\JPEGLosslessNonHierarchical14 + +[CTJPEGLossless] +PresentationContext1 = CTImageStorage\JPEGLossless + +[CTJPEGLSLossless] +PresentationContext1 = CTImageStorage\JPEGLSLossless + +[CTMixedStandardSingle] +PresentationContext1 = CTImageStorage\MixedStandardTS + +[CTAllSupportedSingle] +PresentationContext1 = CTImageStorage\AllSupportedTS + +[CTAllSupportedMultiple] +PresentationContext1 = CTImageStorage\ImplicitLE +PresentationContext2 = CTImageStorage\ExplicitLE +PresentationContext3 = CTImageStorage\ExplicitBE +PresentationContext4 = CTImageStorage\RLE +PresentationContext5 = CTImageStorage\JPEGLosslessNonHierarchical14 +PresentationContext6 = CTImageStorage\JPEGLossless +PresentationContext7 = CTImageStorage\JPEGLSLossless + +[CTMixedCompressedMultiple] +PresentationContext1 = CTImageStorage\RLE +PresentationContext2 = CTImageStorage\JPEGLosslessNonHierarchical14 +PresentationContext3 = CTImageStorage\JPEGLossless +PresentationContext4 = CTImageStorage\JPEGLSLossless + +[CTMixedUnsupportedMultiple] +PresentationContext1 = CTImageStorage\Unsupported1 +PresentationContext2 = CTImageStorage\Unsupported2 +PresentationContext3 = CTImageStorage\Unsupported3 + +[RTAllSupportedSingle] +PresentationContext1 = RTStructureSetStorage\MixedStandardTS + +[RTAllSupportedMultiple] +PresentationContext1 = RTStructureSetStorage\ImplicitLE +PresentationContext2 = RTStructureSetStorage\ExplicitLE +PresentationContext3 = RTStructureSetStorage\ExplicitBE + +[RTCTImplicitLE] +PresentationContext1 = RTStructureSetStorage\ImplicitLE +PresentationContext2 = CTImageStorage\ImplicitLE + +[RTCTExplicitLE] +PresentationContext1 = RTStructureSetStorage\ExplicitLE +PresentationContext2 = CTImageStorage\ExplicitLE + +[RTCTExplicitBE] +PresentationContext1 = RTStructureSetStorage\ExplicitBE +PresentationContext2 = CTImageStorage\ExplicitBE + +[RTCTAllSupportedMultiple] +PresentationContext1 = CTImageStorage\ImplicitLE +PresentationContext2 = CTImageStorage\ExplicitLE +PresentationContext3 = CTImageStorage\ExplicitBE +PresentationContext4 = CTImageStorage\RLE +PresentationContext5 = CTImageStorage\JPEGLosslessNonHierarchical14 +PresentationContext6 = CTImageStorage\JPEGLossless +PresentationContext7 = CTImageStorage\JPEGLSLossless +PresentationContext8 = RTStructureSetStorage\ImplicitLE +PresentationContext9 = RTStructureSetStorage\ExplicitLE +PresentationContext10 = RTStructureSetStorage\ExplicitBE + +# ============================================================================ +[[Profiles]] +# ============================================================================ + +[LEImplicitCT] +PresentationContexts = CTImplicitLE + +[LEExplicitCT] +PresentationContexts = CTExplicitLE + +[BEExplicitCT] +PresentationContexts = CTExplicitBE + +[RLECT] +PresentationContexts = CTRLE + +[JPEGLosslessNonHierarchical14CT] +PresentationContexts = CTJPEGLosslessNonHierarchical14 + +[JPEGLosslessCT] +PresentationContexts = CTJPEGLossless + +[JPEGLSLosslessCT] +PresentationContexts = CTJPEGLSLossless + +[MixedStandardSingleCT] +PresentationContexts = CTMixedStandardSingle + +[MixedCompressedCT] +PresentationContexts = CTMixedCompressedMultiple + +[AllSupportedSingleCT] +PresentationContexts = CTAllSupportedSingle + +[AllSupportedMultipleCT] +PresentationContexts = CTAllSupportedMultiple + +[MixedCompressedMultipleCT] +PresentationContexts = CTMixedCompressedMultiple + +[MixedUnsupportedMultipleCT] +PresentationContexts = CTMixedUnsupportedMultiple + +[AllSupportedSingleRT] +PresentationContexts = RTAllSupportedSingle + +[AllSupportedMultipleRT] +PresentationContexts = RTAllSupportedMultiple + +[LEImplicitRTCT] +PresentationContexts = RTCTImplicitLE + +[LEExplicitRTCT] +PresentationContexts = RTCTExplicitLE + +[BEExplicitRTCT] +PresentationContexts = RTCTExplicitBE + +[AllSupportedMultipleRTCT] +PresentationContexts = RTCTAllSupportedMultiple \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/TestResult.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/TestResult.cs new file mode 100644 index 0000000..9b4c661 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/TestResult.cs @@ -0,0 +1,121 @@ +namespace Microsoft.InnerEye.Listener.Tests.Common.Documentation +{ + using System; + + /// + /// The test result model. + /// + public class TestResult + { + /// + /// The verification document header1 + /// + public static readonly string[] VerificationDocumentHeaders = new[] + { + "# Microsoft Radiomcs: Gateway Test Results", + string.Empty, + "Test|Test Description|Test Result|Test Date Time|Machine Name", + "------------|------------|------------|-------------|-------------", + }; + + /// + /// The divider + /// + private const char Divider = '|'; + + /// + /// Initializes a new instance of the class. + /// + /// Name of the test. + /// The test category. + /// The test description. + /// The test result. + /// The test date time. + /// Name of the machine. + /// The test class. + public TestResult( + string name, + string category, + string description, + string result, + DateTime resultDateTime, + string machineName, + string testClass) + { + Name = name; + Category = category; + Description = description; + Result = result; + ResultDateTime = resultDateTime; + MachineName = machineName; + TestClass = testClass; + } + + /// + /// Gets or sets the name of the test. + /// + /// + /// The name of the test. + /// + public string Name { get; set; } + + /// + /// Gets or sets the test category. + /// + /// + /// The test category. + /// + public string Category { get; set; } + + /// + /// Gets or sets the test description. + /// + /// + /// The test description. + /// + public string Description { get; set; } + + /// + /// Gets or sets the test result. + /// + /// + /// The test result. + /// + public string Result { get; set; } + + /// + /// Gets or sets the test date time. + /// + /// + /// The test date time. + /// + public DateTime ResultDateTime { get; set; } + + /// + /// Gets or sets the name of the machine. + /// + /// + /// The name of the machine. + /// + public string MachineName { get; set; } + + /// + /// Gets or sets the test class. + /// + /// + /// The test class. + /// + public string TestClass { get; set; } + + /// + /// Returns a that represents this instance. + /// + /// + /// A that represents this instance. + /// + public override string ToString() + { + return $"Name: {Name}
Class: {TestClass}
Category: {Category}{Divider}{Description}{Divider}{Result}{Divider}{ResultDateTime}{Divider}{MachineName}"; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/VerificationDocument.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/VerificationDocument.cs new file mode 100644 index 0000000..a6292ea --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Documentation/VerificationDocument.cs @@ -0,0 +1,95 @@ +namespace Microsoft.InnerEye.Listener.Tests.Common.Documentation +{ + using System; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Threading; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + /// + /// Creates a document from the current test context. + /// + public static class VerificationDocument + { + /// + /// Appends the test result to the file path. + /// + /// The document file path. + /// The current test context. + public static void AppendTestResult(string path, TestContext testContext) + { + var testResult = CreateTestResult(testContext).ToString(); + + if (!File.Exists(path)) + { + TryWriteLines(path, 1, TestResult.VerificationDocumentHeaders.Concat(new[] { testResult }).ToArray()); + testContext.WriteLine($"Created verification document: {path}. Result: {testResult}"); + } + else + { + TryWriteLines(path, 5, testResult); + testContext.WriteLine($"Updated verification document: {path}. Result: {testResult}"); + } + + testContext.AddResultFile(path); + } + + /// + /// Tries to write the lines. Sometimes the file might still be use so we wait between retries. + /// + /// The path. + /// The number of times to retry. + /// The lines. + private static void TryWriteLines(string path, int retryCount = 5, params string[] lines) + { + try + { + File.AppendAllLines(path, lines); + } + catch + { + if (retryCount >= 0) + { + Thread.Sleep(1000); + TryWriteLines(path, retryCount - 1, lines); + } + } + } + + /// + /// Creates the test result from the current test context. + /// + /// The test context. + /// The test result. + private static TestResult CreateTestResult(TestContext testContext) + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + + foreach (var assembly in assemblies) + { + var classType = assembly.GetTypes().FirstOrDefault(x => x.FullName == testContext.FullyQualifiedTestClassName); + + if (classType != null) + { + var method = classType.GetMethod(testContext.TestName); + + var categories = method.GetCustomAttribute(true); + var description = method.GetCustomAttributes(true); + + return new TestResult( + testContext.TestName, + categories == null ? string.Empty : string.Join(",", categories.TestCategories.Select(x => x)), + description == null ? string.Empty : string.Join(",", description.Select(x => x.Description)), + testContext.CurrentTestOutcome.ToString(), + DateTime.UtcNow, + Environment.MachineName, + classType.Name); + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/DcmtkHelpers.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/DcmtkHelpers.cs new file mode 100644 index 0000000..ac1e994 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/DcmtkHelpers.cs @@ -0,0 +1,133 @@ +namespace Microsoft.InnerEye.Listener.Tests.Common.Helpers +{ + using System.Diagnostics; + using System.IO; + using System.Reflection; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + public enum ScuProfile + { + LEImplicitCT, + LEExplicitCT, + MixedUnsupportedMultipleCT, + BEExplicitCT, + AllSupportedMultipleCT, + AllSupportedMultipleRTCT, + RLECT, + JPEGLosslessCT, + JPEGLosslessNonHierarchical14CT, + JPEGLSLosslessCT, + MixedStandardSingleCT, + LEImplicitRTCT, + LEExplicitRTCT, + BEExplicitRTCT, + } + + public class DcmtkHelpers + { + private static string StoreSCUPath { get; } = "Assets\\storescu.exe"; + + /// + /// Sends an entire folder using the DCMTK Store SCU function. + /// + /// The file path. + /// The port number of the receiver. + /// The SCU profile. + /// The tesxt context for logging information. + /// If we should scan sub-directories for files. + /// If we should wait for the send to complete. + /// If we should abort. + /// The application entity title. + /// The called application entity title. + /// The host IP. + /// The result for the Store SCU function. This string will be empty if it completed succesfully. + public static string SendFolderUsingDCMTK(string path, int port, ScuProfile scuProfile, TestContext testContext, bool scanDirectories = true, bool waitForExit = true, bool abort = false, string applicationEntityTitle = "STORESCU", string calledAETitle = "ANY-SCP", string hostIP = "127.0.0.1") + { + var directoryPath = path; + + if (!directoryPath.EndsWith(Path.DirectorySeparatorChar.ToString())) + { + directoryPath += Path.DirectorySeparatorChar; + } + + // Make sure the directory path ends with double / so it is not escaped + directoryPath += Path.DirectorySeparatorChar; + + Assert.IsTrue(new DirectoryInfo(directoryPath).Exists, $"[DCMTK StoreSCU] Directory {directoryPath} does not exist"); + + return SendUsingDCMTK(directoryPath, port, scuProfile, testContext, scanDirectories, waitForExit, abort, applicationEntityTitle, calledAETitle, hostIP); + } + + /// + /// Sends a Dicom file using the DCMTK Store SCU function. + /// + /// The file path. + /// The port number of the receiver. + /// The SCU profile. + /// The tesxt context for logging information. + /// If we should wait for the send to complete. + /// If we should abort. + /// The application entity title. + /// The called application entity title. + /// The host IP. + /// The result for the Store SCU function. This string will be empty if it completed succesfully. + public static string SendFileUsingDCMTK(string path, int port, ScuProfile scuProfile, TestContext testContext, bool waitForExit = true, bool abort = false, string applicationEntityTitle = "STORESCU", string calledAETitle = "ANY-SCP", string hostIP = "127.0.0.1") + { + var filePath = path; + + if (filePath.StartsWith(Path.DirectorySeparatorChar.ToString())) + { + var currentExecutionLocation = (new DirectoryInfo(Assembly.GetExecutingAssembly().Location)).Parent.FullName; + filePath = currentExecutionLocation + filePath; + } + + Assert.IsTrue(new FileInfo(filePath).Exists, $"[DCMTK StoreSCU] File {filePath} does not exist"); + + return SendUsingDCMTK(filePath, port, scuProfile, testContext, false, waitForExit, abort, applicationEntityTitle, calledAETitle, hostIP); + } + + private static string SendUsingDCMTK(string path, int port, ScuProfile scuProfile, TestContext testContext, bool scanDirectories, bool waitForExit, bool abort, string applicationEntityTitle, string calledAETitle, string hostIP) + { + Assert.IsFalse(string.IsNullOrWhiteSpace(path)); + + var currentExecutionLocation = (new DirectoryInfo(Assembly.GetExecutingAssembly().Location)).Parent.FullName; + + Assert.IsNotNull(StoreSCUPath, "storescu.exe not found on system PATH"); + var fileName = StoreSCUPath; + + testContext?.WriteLine($"Launching DCMTK StoreSCU from: {fileName}"); + + var logLevel = "-ll error"; + var sd = scanDirectories ? " +sd +r +sp \"*.dcm\"" : ""; + var abortS = abort ? " --abort" : ""; + var configPath = Path.Combine(currentExecutionLocation, "Assets\\SCU.cfg"); + var scuProfileString = scuProfile.ToString(); + + Assert.IsTrue(new FileInfo(configPath).Exists, $"[DCMTK StoreSCU] The config path is incorrect: {configPath}"); + + testContext?.WriteLine($"Sending file via DCMTK StoreSCU from: {path}"); + + var process = new Process(); + + process.StartInfo.FileName = fileName; + process.StartInfo.Arguments = $"{logLevel}{abortS}{sd} -aec {calledAETitle} -aet {applicationEntityTitle} -xf \"{configPath}\" {scuProfileString} {hostIP} {port} \"{path}\""; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardOutput = true; + + testContext?.WriteLine($"DCMTK StoreSCU start arguments: {process.StartInfo.Arguments}"); + + process.Start(); + + if (waitForExit) + { + var stdOut = process.StandardOutput.ReadToEnd(); + process.WaitForExit(); + + return stdOut; + } + + return string.Empty; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/MSMQHelpers.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/MSMQHelpers.cs new file mode 100644 index 0000000..2f8cf1f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Helpers/MSMQHelpers.cs @@ -0,0 +1,38 @@ +namespace Microsoft.InnerEye.Listener.Tests.Helpers +{ + using System; + using System.Runtime.InteropServices; + + public partial class MSMQHelpers + { + [DllImport("kernel32")] + private static extern IntPtr LoadLibrary(string lpFileName); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool FreeLibrary(IntPtr hModule); + + /// + /// Checks if MSMQ is installed. If the function returns false, please run the following command in an elevated PowerShell command prompt: + /// Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Server -All + /// + /// If MSMQ installed. + public static bool IsMSMQInstalled() + { + try + { + var handle = LoadLibrary("Mqrt.dll"); + + if (handle != IntPtr.Zero && handle.ToInt32() != 0) + { + FreeLibrary(handle); + return true; + } + } + catch + { + } + + return false; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Microsoft.InnerEye.Listener.Tests.Common.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Microsoft.InnerEye.Listener.Tests.Common.csproj new file mode 100644 index 0000000..511f33f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests.Common/Microsoft.InnerEye.Listener.Tests.Common.csproj @@ -0,0 +1,166 @@ + + + net462 + x64 + Microsoft.InnerEye.Listener.Tests.Common + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Common test project for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + false + + + + + + + + Assets\charls.dll + PreserveNewest + + + Assets\cmr.dll + PreserveNewest + + + Assets\dcmdata.dll + PreserveNewest + + + Assets\dcmdsig.dll + PreserveNewest + + + Assets\dcmdump.exe + PreserveNewest + + + Assets\dcmfg.dll + PreserveNewest + + + Assets\dcmimage.dll + PreserveNewest + + + Assets\dcmimgle.dll + PreserveNewest + + + Assets\dcmiod.dll + PreserveNewest + + + Assets\dcmjpeg.dll + PreserveNewest + + + Assets\dcmjpls.dll + PreserveNewest + + + Assets\dcmnet.dll + PreserveNewest + + + Assets\dcmpmap.dll + PreserveNewest + + + Assets\dcmpstat.dll + PreserveNewest + + + Assets\dcmqrdb.dll + PreserveNewest + + + Assets\dcmrt.dll + PreserveNewest + + + Assets\dcmseg.dll + PreserveNewest + + + Assets\dcmsr.dll + PreserveNewest + + + Assets\dcmtls.dll + PreserveNewest + + + Assets\dcmtract.dll + PreserveNewest + + + Assets\dcmwlm.dll + PreserveNewest + + + Assets\i2d.dll + PreserveNewest + + + Assets\ijg12.dll + PreserveNewest + + + Assets\ijg16.dll + PreserveNewest + + + Assets\ijg8.dll + PreserveNewest + + + Assets\oflog.dll + PreserveNewest + + + Assets\ofstd.dll + PreserveNewest + + + Assets\storescu.exe + PreserveNewest + + + Assets\dicom3tools\cyggcc_s-1.dll + PreserveNewest + + + Assets\dicom3tools\cygstdc++-6.dll + PreserveNewest + + + Assets\dicom3tools\cygwin1.dll + PreserveNewest + + + Assets\dicom3tools\dciodvfy.exe + PreserveNewest + + + Assets\dicom3tools\dciodvfy.exe.local + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/BaseTestClass.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/BaseTestClass.cs new file mode 100644 index 0000000..9ce608b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/BaseTestClass.cs @@ -0,0 +1,692 @@ +namespace Microsoft.InnerEye.Listener.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Net; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + + using Markdig; + using Microsoft.Extensions.Logging; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.Gateway.MessageQueueing; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.InnerEye.Listener.Common.Providers; + using Microsoft.InnerEye.Listener.Common.Services; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.Processor.Services; + using Microsoft.InnerEye.Listener.Receiver.Services; + using Microsoft.InnerEye.Listener.Tests.Models; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using TheArtOfDev.HtmlRenderer.PdfSharp; + + /// + /// The base test class. + /// + [TestClass] + public class BaseTestClass + { + /// + /// Logger for common use. + /// + protected readonly ILogger _baseTestLogger; + + /// + /// Logger for the configuration service. + /// + private readonly ILogger _configurationLogger; + + /// + /// Logger for the delete service. + /// + private readonly ILogger _deleteLogger; + + /// + /// Logger for the download service. + /// + private readonly ILogger _downloadLogger; + + /// + /// Logger for the push service. + /// + private readonly ILogger _pushLogger; + + /// + /// Logger for the receive service. + /// + private readonly ILogger _receiveLogger; + + /// + /// Logger for the upload service. + /// + private readonly ILogger _uploadLogger; + + /// + /// Gets or sets the test context. + /// + public TestContext TestContext { get; set; } + + /// + /// Gets a test queue path. + /// + private const string TestQueuePath = @".\private$\TestQueue1"; + + /// + /// Gets the test upload queue path. + /// + private const string TestUploadQueuePath = @".\private$\ListenerTestUpload"; + + /// + /// Gets the test download queue path. + /// + private const string TestDownloadQueuePath = @".\private$\ListenerTestDownload"; + + /// + /// Gets the test push queue path. + /// + private const string TestPushQueuePath = @".\private$\ListenerTestPush"; + + /// + /// The test delete queue path + /// + private const string TestDeleteQueuePath = @".\private$\DeleteTest"; + + /// + /// Create a new, random, message queue path. + /// + /// + private static string GetUniqueMessageQueuePath() => $@".\Private$\{Guid.NewGuid()}"; + + /// + /// Folder containing test configurations. + /// + private readonly string _basePathConfigs = "TestConfigurations"; + + /// + /// The temporary directories created during a test. The clean up method will also clean these up. + /// + private readonly IList temporaryDirectories = new List(); + + /// + /// AET configs as loaded from _basePathConfigs. + /// + private AETConfigProvider _testAETConfigProvider; + + /// + /// GatewayProcessorConfigProvider as loaded from _basePathConfigs. + /// + protected GatewayProcessorConfigProvider TestGatewayProcessorConfigProvider { get; } + + /// + /// GatewayReceiveConfigProvider as loaded from _basePathConfigs. + /// + protected GatewayReceiveConfigProvider TestGatewayReceiveConfigProvider { get; } + + /// + /// Initializes a new instance of the class. + /// + public BaseTestClass() + { + ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; + + var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole()); + _baseTestLogger = loggerFactory.CreateLogger("BaseTest"); + _configurationLogger = loggerFactory.CreateLogger("ConfigurationService"); + _deleteLogger = loggerFactory.CreateLogger("DeleteService"); + _downloadLogger = loggerFactory.CreateLogger("DownloadService"); + _pushLogger = loggerFactory.CreateLogger("PushService"); + _receiveLogger = loggerFactory.CreateLogger("ReceiveService"); + _uploadLogger = loggerFactory.CreateLogger("UploadService"); + + // Set a logger for fo-dicom network operations so that they show up in VS output when debugging + Dicom.Log.LogManager.SetImplementation(new Dicom.Log.TextWriterLogManager(new DataProviderTests.DebugTextWriter())); + + _testAETConfigProvider = new AETConfigProvider(loggerFactory.CreateLogger("ModelSettings"), _basePathConfigs); + TestGatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(loggerFactory.CreateLogger("ProcessorSettings"), _basePathConfigs); + TestGatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(loggerFactory.CreateLogger("ProcessorSettings"), _basePathConfigs); + } + + [TestInitialize] + public virtual void TestSetup() + { + TryDeleteDirectory(Path.Combine(Path.GetTempPath(), "InnerEyeListenerTestsTemp")); + + TryKillAnyZombieProcesses(); + + ClearQueues(TestQueuePath, TestPushQueuePath, TestDownloadQueuePath, TestUploadQueuePath, TestDeleteQueuePath); + } + + [TestCleanup] + public virtual void TestCleanUp() + { + // Uncomment if redistribution of anonymization protocol or DCMTK tools are needed + /* + var (contents, extension) = ConvertMarkdownToPdf(CreateAnonymisationProtocol()); + var (readmeContents, readmeExtension) = ConvertMarkdownToPdf(File.ReadAllText(@"Assets\README.md")); + + // Write Store-SCU and DCMDump to the package folder + WriteFileForBuildPackage(@"Assets\storescu.exe", "DicomTools"); + WriteFileForBuildPackage(@"Assets\dcmdump.exe", "DicomTools"); + WriteFileForBuildPackage(@"Assets\DCMTKLicense.txt", "DicomTools"); + WriteFileForBuildPackage($@"Assets\DataIngestionAnonymisationProtocol.pdf", "AnonymisationProtocols"); + WriteForBuildPackage($"Readme{readmeExtension}", string.Empty, readmeContents); + WriteForBuildPackage($"SegmentationServiceAnonymisationProtocol{extension}", "AnonymisationProtocols", contents); + + // Convert all output MD files to HTML for easier reading + ConvertAllOutputMarkdownFilesToHtml(); + */ + + foreach (var directory in temporaryDirectories) + { + TryDeleteDirectory(directory); + } + + TryDeleteDirectory(Path.Combine(Path.GetTempPath(), "InnerEyeListenerTestsTemp")); + + TryKillAnyZombieProcesses(); + } + + protected void WriteDicomFileForBuildPackage(string fileName, DicomFile dicomFile) + { + var path = GetBuildPackageResultPath(fileName, "AnonymisationProtocols"); + + dicomFile.Save(path); + + TestContext.AddResultFile(path); + TestContext.WriteLine($"Written Dicom file to path: {path}"); + } + + /// + /// One minute in seconds. + /// + protected const int OneMinSecs = 60; + + /// + /// 15 minutues in seconds. + /// + protected const int QuarterHourSecs = 15 * OneMinSecs; + + /// + /// One hour in seconds. + /// + protected const int OneHourSecs = 60 * OneMinSecs; + + protected DequeueServiceConfig GetTestDequeueServiceConfig( + uint maximumQueueMessageAgeSeconds = 100, + uint deadLetterMoveFrequencySeconds = 1) => + new DequeueServiceConfig(maximumQueueMessageAgeSeconds, deadLetterMoveFrequencySeconds); + + /// + /// Converts all markdown files in the output directory to HTML. + /// This is easier to read out of the box than the markdown files. + /// + protected void ConvertAllOutputMarkdownFilesToHtml() + { + var directory = new DirectoryInfo(GetBuildPackageResultPath(string.Empty, string.Empty)); + + if (!directory.Exists) + { + return; + } + + var markdownFiles = directory.GetFiles("*.*", SearchOption.AllDirectories).Where(x => x.Extension == ".md"); + + foreach (var markdownFile in markdownFiles) + { + try + { + var (contents, extension) = ConvertMarkdownToPdf(File.ReadAllText(markdownFile.FullName)); + + var resultPath = Path.ChangeExtension(markdownFile.FullName, extension); + + // Convert markdown file to html and replace extension. + File.WriteAllBytes(resultPath, contents); + TestContext.AddResultFile(resultPath); + } + catch (Exception e) + { + _baseTestLogger.LogError(e, $"Failed to convert file to HTML. File {markdownFile}"); + } + } + } + + /// + /// Converts markdown. + /// + /// The markdown to convert. + /// The contents and the resulting file extension. + private static (byte[] Contents, string Extension) ConvertMarkdownToPdf(string markdown) + { + // Convert markdown file to html + var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build(); + var html = Markdown.ToHtml(markdown, pipeline).Replace("", "
"); + + // Convert HTML to pdf + using (var memoryStream = new MemoryStream()) + { + var pdf = PdfGenerator.GeneratePdf(html, PdfSharp.PageSize.A4); + pdf.Save(memoryStream); + + return (memoryStream.ToArray(), ".pdf"); + } + } + + protected static string GetBuildPackageResultPath(string fileName, string subDirectory = null) + { + var buildSourceDirectory = Environment.GetEnvironmentVariable("BUILD_SOURCESDIRECTORY"); + + if (string.IsNullOrWhiteSpace(buildSourceDirectory)) + { + buildSourceDirectory = @"C:"; + } + + var directory = $@"{buildSourceDirectory}\TestResultFiles\{subDirectory}"; + + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + + return $@"{directory}\{fileName}"; + } + + /// + /// Clears all the queues and its corresponding dead letter queue. + /// + /// Queue paths. + private static void ClearQueues(params string[] queuePaths) + { + foreach (var path in queuePaths) + { + // Note that this must be the same as DequeueClientServiceBase.DeadLetterQueuePathFormat. + using (var deadLetterQueue = GatewayMessageQueue.Get(DequeueServiceConfig.DeadLetterQueuePath(path))) + using (var queue = GatewayMessageQueue.Get(path)) + { + deadLetterQueue.Clear(); + queue.Clear(); + } + } + } + + /// + /// Get a test message queue. + /// + /// Test IMessageQueue. + protected static IMessageQueue GetTestMessageQueue() => GatewayMessageQueue.Get(TestQueuePath); + + /// + /// Get a unique test message queue. + /// + /// Unique test IMessageQueue. + protected static IMessageQueue GetUniqueMessageQueue() => GatewayMessageQueue.Get(GetUniqueMessageQueuePath()); + + protected DirectoryInfo CreateTemporaryDirectory() + { + var result = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), $@"InnerEyeListenerTestsTemp\{Guid.NewGuid().ToString()}")); + temporaryDirectories.Add(result.FullName); + + return result; + } + + protected IEnumerable SegmentationAnonymisationProtocol() + { + using (var segmentationClient = TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient().Invoke()) + { + return segmentationClient.SegmentationAnonymisationProtocol; + } + } + + protected MockInnerEyeSegmentationClient GetMockInnerEyeSegmentationClient() + { + var realClient = (InnerEyeSegmentationClient)TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient().Invoke(); + return new MockInnerEyeSegmentationClient(realClient); + } + + protected AETConfigModel GetTestAETConfigModel() => + _testAETConfigProvider.GetAETConfigs().First(); + + protected ReceiveServiceConfig GetTestGatewayReceiveConfig() => + new ReceiveServiceConfig( + new DicomEndPoint("RGatewayT", 105, "127.0.0.1"), + CreateTemporaryDirectory().FullName, + BuildConfigAcceptedSopClassesAndTransferSyntaxes()); + + protected static void TransactionalEnqueue(IMessageQueue InnerEyeMessageQueue, T value) + { + using (var queueTransaction = InnerEyeMessageQueue.CreateQueueTransaction()) + { + try + { + queueTransaction.Begin(); + InnerEyeMessageQueue.Enqueue(value, queueTransaction); + queueTransaction.Commit(); + } + catch (Exception) + { + queueTransaction.Abort(); + throw; + } + } + } + + protected static T TransactionalDequeue(IMessageQueue messageQueue, int timeoutMs = 2000) + { + var startTime = DateTime.UtcNow; + + while ((DateTime.UtcNow - startTime).TotalMilliseconds < timeoutMs) + { + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + var result = TryDequeue(messageQueue, queueTransaction, timeoutMs); + queueTransaction.Commit(); + + return result; + } + } + + throw new MessageQueueReadException("Failed to transactional dequeue."); + } + + protected static T TryDequeue(IMessageQueue messageQueue, IQueueTransaction messageQueueTransaction, int timeoutMs = 2000) + { + var startTime = DateTime.UtcNow; + + while ((DateTime.UtcNow - startTime).TotalMilliseconds < timeoutMs) + { + try + { + return messageQueue.DequeueNextMessage(messageQueueTransaction); + } + catch (Exception) + { + Task.WaitAll(Task.Delay(500)); + } + } + + throw new MessageQueueReadException("Failed to transactional dequeue."); + } + + protected void TryDeleteDirectory(string directory) + { + var directoryInfo = new DirectoryInfo(directory); + + if (directoryInfo.Exists) + { + try + { + directoryInfo.Delete(true); + } + catch (Exception e) + { + TestContext.WriteLine($"Failed to delete directory {directory} with exception {e}"); + } + } + } + + private static Dictionary BuildConfigAcceptedSopClassesAndTransferSyntaxes() + { + return BuildAcceptedSopClassesAndTransferSyntaxes().ToDictionary(kvp => kvp.Key.UID, kvp => kvp.Value.Select(x => x.UID.UID).ToArray()); + } + + /// + /// Constructs the set of DICOM services we support in InnerEye and the preferred Transfer Syntaxes + /// for those services. + /// + /// + protected static Dictionary BuildAcceptedSopClassesAndTransferSyntaxes() + { + // Syntaxes we accept for the Verification SOP (aka C-Echo) class in order of preference + DicomTransferSyntax[] acceptedVerificationSyntaxes = + { + DicomTransferSyntax.ExplicitVRLittleEndian, + DicomTransferSyntax.ExplicitVRBigEndian, + DicomTransferSyntax.ImplicitVRLittleEndian + }; + + // For RT Storage, we accept the following in order of preference + DicomTransferSyntax[] acceptedRTransferSyntaxs = + { + DicomTransferSyntax.ImplicitVRLittleEndian, + DicomTransferSyntax.ExplicitVRLittleEndian, + DicomTransferSyntax.ExplicitVRBigEndian + }; + + // For CT and MR, we accept the following in order of preference + DicomTransferSyntax[] acceptedImageTransferSyntaxs = + { + // Uncompressed + DicomTransferSyntax.ImplicitVRLittleEndian, + DicomTransferSyntax.ExplicitVRLittleEndian, + DicomTransferSyntax.ExplicitVRBigEndian, + + // Lossless + DicomTransferSyntax.JPEGProcess14, //57 + DicomTransferSyntax.JPEGProcess14SV1, //70 + DicomTransferSyntax.JPEGLSLossless, //80 + DicomTransferSyntax.RLELossless + }; + + // Build the set of accepted SOP Classes and their transfer syntaxes + return new Dictionary + { + { DicomUID.Verification, acceptedVerificationSyntaxes }, + { DicomUID.RTStructureSetStorage, acceptedRTransferSyntaxs }, + { DicomUID.CTImageStorage, acceptedImageTransferSyntaxs }, + { DicomUID.MRImageStorage, acceptedImageTransferSyntaxs } + }; + } + + /// + /// Create a new instance of the class. + /// + /// Optional InnerEye segmentation client. + /// Configuration service config callback. + /// The services. + /// New ConfigurationService. + protected ConfigurationService CreateConfigurationService( + IInnerEyeSegmentationClient innerEyeSegmentationClient = null, + Func getConfigurationServiceConfig = null, + params IService[] services) => + new ConfigurationService( + innerEyeSegmentationClient != null ? () => innerEyeSegmentationClient : TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(), + getConfigurationServiceConfig ?? TestGatewayProcessorConfigProvider.ConfigurationServiceConfig, + _configurationLogger, + services); + + /// + /// Create a new instance of the class. + /// + /// Optional dequeue service config. + /// New DeleteService. + protected DeleteService CreateDeleteService( + DequeueServiceConfig dequeueServiceConfig = null) => + new DeleteService( + TestDeleteQueuePath, + () => dequeueServiceConfig != null ? dequeueServiceConfig : GetTestDequeueServiceConfig(), + _deleteLogger); + + /// + /// Creates a new instance of the class. + /// + /// Optional InnerEye segmentation client. + /// Optional download wait timeout for download service config. + /// Optional dequeue service config. + /// The number of concurrent execution instances we should have. + /// New DownloadService. + protected DownloadService CreateDownloadService( + IInnerEyeSegmentationClient innerEyeSegmentationClient = null, + int? downloadWaitTimeoutInSeconds = null, + DequeueServiceConfig dequeueServiceConfig = null, + int instances = 1) => + new DownloadService( + innerEyeSegmentationClient != null ? () => innerEyeSegmentationClient : TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(), + TestDownloadQueuePath, + TestPushQueuePath, + TestDeleteQueuePath, + () => new DownloadServiceConfig(downloadWaitTimeoutInSeconds), + () => dequeueServiceConfig != null ? dequeueServiceConfig : GetTestDequeueServiceConfig(), + _downloadLogger, + instances); + + /// + /// Creates a new instance of the class. + /// + /// AET configuration provider. + /// New PushService + protected PushService CreatePushService( + Func> aetConfigProvider = null) => + new PushService( + aetConfigProvider ?? _testAETConfigProvider.GetAETConfigs, + new DicomDataSender(), + TestPushQueuePath, + TestDeleteQueuePath, + () => GetTestDequeueServiceConfig(), + _pushLogger, + instances: 1); + + /// + /// Initializes a new instance of the class. + /// + /// Callback to get configuration. + /// New ReceiveService. + protected ReceiveService CreateReceiveService( + Func getReceiveServiceConfig) => + new ReceiveService( + getReceiveServiceConfig, + TestUploadQueuePath, + _receiveLogger); + + /// + /// Creates a new instance of the class. + /// + /// Optional InnerEye segmentation client. + /// AET configuration provider. + /// The number of concurrent execution instances we should have. + /// New UploadService. + protected UploadService CreateUploadService( + IInnerEyeSegmentationClient innerEyeSegmentationClient = null, + Func> aetConfigProvider = null, + int instances = 1) => + new UploadService( + innerEyeSegmentationClient != null ? () => innerEyeSegmentationClient : TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(), + aetConfigProvider ?? _testAETConfigProvider.GetAETConfigs, + TestUploadQueuePath, + TestDownloadQueuePath, + TestDeleteQueuePath, + () => GetTestDequeueServiceConfig(), + _uploadLogger, + instances); + + protected async Task<(string SegmentationId, string ModelId, IEnumerable Data)> StartRealSegmentationAsync(string filesPath) + { + var dicomFiles = new DirectoryInfo(filesPath).GetFiles().Select(x => DicomFile.Open(x.FullName)).ToArray(); + + using (var segmentationClient = TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient().Invoke()) + { + var testAETConfigModel = GetTestAETConfigModel(); + + var matchedModel = ApplyAETModelConfigProvider.ApplyAETModelConfig(testAETConfigModel.AETConfig.Config.ModelsConfig, dicomFiles); + var modelId = matchedModel.Result.ModelId; + + var startSegmentationResult = await segmentationClient.StartSegmentationAsync( + matchedModel.Result.ModelId, + matchedModel.Result.ChannelData); + + var referenceDicomFiles = startSegmentationResult.postedImages.CreateNewDicomFileWithoutPixelData(segmentationClient.SegmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + return (startSegmentationResult.segmentationId, modelId, referenceDicomFiles); + } + } + + protected async Task<(string SegmentationId, string ModelId, IEnumerable Data)> StartFakeSegmentationAsync(string filesPath) + { + var dicomFiles = new DirectoryInfo(filesPath).GetFiles().Select(x => DicomFile.Open(x.FullName)).ToArray(); + + var segmentationClient = GetMockInnerEyeSegmentationClient(); + segmentationClient.RealSegmentation = false; + + var testAETConfigModel = GetTestAETConfigModel(); + + var matchedModel = ApplyAETModelConfigProvider.ApplyAETModelConfig(testAETConfigModel.AETConfig.Config.ModelsConfig, dicomFiles); + var modelId = matchedModel.Result.ModelId; + + var startSegmentationResult = await segmentationClient.StartSegmentationAsync( + matchedModel.Result.ModelId, + matchedModel.Result.ChannelData); + + var referenceDicomFiles = startSegmentationResult.postedImages.CreateNewDicomFileWithoutPixelData(segmentationClient.SegmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + return (startSegmentationResult.segmentationId, modelId, referenceDicomFiles); + } + + protected static void WaitUntilNoMessagesOnQueue(IMessageQueue queue, int timeoutMs = 60000) + { + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => + { + using (var messageQueueTransaction = queue.CreateQueueTransaction()) + { + messageQueueTransaction.Begin(); + + try + { + queue.DequeueNextMessage(messageQueueTransaction); + messageQueueTransaction.Abort(); + + Task.WaitAll(Task.Delay(500)); + + return false; + } + catch (MessageQueueReadException) + { + messageQueueTransaction.Abort(); + + return true; + } + } + }, + TimeSpan.FromMilliseconds(timeoutMs)); + } + + protected static void Enqueue(IMessageQueue queue, T message, bool clearQueue) + { + if (clearQueue) + { + queue.Clear(); + } + + using (var queueTransaction = queue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + queue.Enqueue(message, queueTransaction); + queueTransaction.Commit(); + } + } + + private void TryKillAnyZombieProcesses() + { + var zombieProcesses = Process.GetProcesses()?.Where(x => x.ProcessName == "storescu")?.ToList(); + + foreach (var process in zombieProcesses) + { + try + { + process.Kill(); + } + catch (Exception e) + { + TestContext.WriteLine($"Failed to kill process {process.Id}, {process.ProcessName} with exception {e}"); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ConfigurationProviderTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ConfigurationProviderTests.cs new file mode 100644 index 0000000..1e7c203 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ConfigurationProviderTests.cs @@ -0,0 +1,807 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.DicomConstraints; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.InnerEye.Listener.Common.Providers; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + [TestClass] + public class ConfigurationProviderTests : BaseTestClass + { + /// + /// Save a data structure of type T to filename in folder. + /// + /// Data type. + /// Instance of T. + /// Folder path to serialise to. + /// Filename to serialise to. + /// True to format the JSON in a human readable way. + public static void Serialise(T t, string folder, string filename, bool prettyPrint = false) + { + var serializerSettings = new JsonSerializerSettings() + { + Converters = new[] { new StringEnumConverter() }, + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }; + + var path = Path.Combine(folder, filename); + var jsonText = prettyPrint ? JsonConvert.SerializeObject(t, serializerSettings) : JsonConvert.SerializeObject(t); + File.WriteAllText(path, jsonText); + } + + /// + /// List of chars to use for random string generation. + /// + public const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + + /// + /// Generate a random bool. + /// + /// Random. + /// Random bool. + public static bool RandomBool(Random random) => random.Next(2) == 1; + + /// + /// Generate a random enum. + /// + /// Enum type. + /// Random. + /// Random element of enum T. + public static T RandomEnum(Random random) + { + var values = Enum.GetValues(typeof(T)); + + return (T)values.GetValue(random.Next(values.Length)); + } + + /// + /// Generate a random string of target length. + /// + /// Random. + /// Target string length. + /// Random string. + public static string RandomString(Random random, int length = 6) + { + var s = new StringBuilder(length); + + for (var i = 0; i < length; i++) + { + s.Append(chars[random.Next(chars.Length)]); + } + + return s.ToString(); + } + + /// + /// Generate a random unsigned short. + /// + /// Random. + /// Random unsigned short. + public static ushort RandomUShort(Random random) => + (ushort)random.Next(0, 65535); + + /// + /// Generate random list of . + /// + /// Array type. + /// Random. + /// Limit nesting on group tags. + /// Count of models to create. + /// Callback to creat + /// New list of ModelConstraintsConfig. + public static T[] RandomArray(Random random, int maxDepth, int count, Func createRandomT) + { + var list = new T[count]; + + for (var i = 0; i < count; i++) + { + list[i] = createRandomT(random, maxDepth); + } + + return list; + } + + /// + /// Pick a random function from a list and invoke it. + /// + /// Return type. + /// Random. + /// Limit nesting on group tags. + /// List of functions taking Random, returning T. + /// New random T. + public static T RandomItem(Random random, int maxDepth, Func[] createRandomTs) => + createRandomTs[random.Next(0, createRandomTs.Length)].Invoke(random, maxDepth); + + /// + /// Generate random . + /// + /// Random. + /// Random ServiceSettings. + public static ServiceSettings RandomServiceSettings(Random random) => + new ServiceSettings(RandomBool(random)); + + /// + /// Generate random . + /// + /// Random. + /// Random DicomEndPoint. + public static DicomEndPoint RandomDicomEndPoint(Random random) => + new DicomEndPoint( + RandomString(random, 10), + random.Next(101, 1000), + RandomString(random, 12)); + + /// + /// Generate random accepted Sop classes and transfer syntaxes. + /// + /// Random. + /// Key count to generate. + /// Value count per key to generate. + /// Dictionary of string to string array. + public static Dictionary RandomAcceptedSopClassesAndTransferSyntaxes( + Random random, int keyCount = 3, int valueCount = 5) + { + var dictionary = new Dictionary(); + + for (var key = 0; key < keyCount; key++) + { + var list = new string[valueCount]; + + for (var value = 0; value < valueCount; value++) + { + list[value] = RandomString(random); + } + + dictionary.Add(RandomString(random, 5), list); + } + + return dictionary; + } + + /// + /// Generate random . + /// + /// Random. + /// Random ReceiveServiceConfig. + public static ReceiveServiceConfig RandomReceiveServiceConfig(Random random) => + new ReceiveServiceConfig( + RandomDicomEndPoint(random), + RandomString(random, 24), + RandomAcceptedSopClassesAndTransferSyntaxes(random)); + + /// + /// Generate random . + /// + /// Random. + /// Random ConfigurationServiceConfig. + public static ConfigurationServiceConfig RandomConfigurationServiceConfig(Random random) => + new ConfigurationServiceConfig( + DateTime.UtcNow.AddSeconds(5), + DateTime.UtcNow.AddSeconds(10), + random.Next(61, 3600)); + + /// + /// Generate random . + /// + /// Random. + /// Random GatewayReceiveConfig. + public static GatewayReceiveConfig RandomGatewayReceiveConfig(Random random) => + new GatewayReceiveConfig( + RandomServiceSettings(random), + RandomReceiveServiceConfig(random), + RandomConfigurationServiceConfig(random)); + + /// + /// Generate random . + /// + /// Random. + /// Random ProcessorSettings. + public static ProcessorSettings RandomProcessorSettings(Random random) => + new ProcessorSettings( + RandomString(random, 12), + new Uri("https://" + RandomString(random, 8) + ".com")); + + /// + /// Generate random . + /// + /// Random. + /// Random DequeueServiceConfig. + public static DequeueServiceConfig RandomDequeueServiceConfig(Random random) => + new DequeueServiceConfig(random.Next(202, 299), random.Next(302, 399)); + + /// + /// Generate random . + /// + /// Random. + /// Random DownloadServiceConfig. + public static DownloadServiceConfig RandomDownloadServiceConfig(Random random) => + new DownloadServiceConfig(random.Next(2, 99), random.Next(102, 199)); + + /// + /// Generate random . + /// + /// Random. + /// Random GatewayProcessorConfig. + public static GatewayProcessorConfig RandomGatewayProcessorConfig(Random random) => + new GatewayProcessorConfig( + RandomServiceSettings(random), + RandomProcessorSettings(random), + RandomDequeueServiceConfig(random), + RandomDownloadServiceConfig(random), + RandomConfigurationServiceConfig(random)); + + /// + /// Generate random . + /// + /// Random. + /// Random DicomTagIndex. + public static DicomTagIndex RandomDicomTagIndex(Random random) => + new DicomTagIndex(RandomUShort(random), RandomUShort(random)); + + /// + /// Generate random . + /// + /// Random. + /// Random DicomOrderedTag. + public static DicomOrderedTag RandomDicomOrderedTagDateTime(Random random) => + new DicomOrderedTag( + RandomEnum(random), + DateTime.UtcNow.AddDays(random.NextDouble() * 1000.0), + random.Next(100)); + + /// + /// Generate random . + /// + /// Doubles are only created with two decimal places, because it is not expected that very + /// high accuracy will not be used in actual configuration files, and the round-trip to and from JSON + /// sometimes fails. + /// Random. + /// Random DicomOrderedTag. + public static DicomOrderedTag RandomDicomOrderedTagDouble(Random random) => + new DicomOrderedTag( + RandomEnum(random), + Math.Round(random.NextDouble() * 1000.0, 2), + random.Next(100)); + + /// + /// Generate random . + /// + /// Random. + /// Random DicomOrderedTag. + public static DicomOrderedTag RandomDicomOrderedTagInt(Random random) => + new DicomOrderedTag( + RandomEnum(random), + random.Next(101, 200), + random.Next(100)); + + /// + /// Generate random . + /// + /// Random. + /// Random DicomOrderedTag. + public static DicomOrderedTag RandomDicomOrderedTagString(Random random) => + new DicomOrderedTag( + RandomEnum(random), + new OrderedString(RandomString(random, 12)), + random.Next(100)); + + /// + /// Generate random . + /// + /// Random. + /// Random DicomOrderedTag. + public static DicomOrderedTag RandomDicomOrderedTagTimeSpan(Random random) => + new DicomOrderedTag( + RandomEnum(random), + TimeSpan.FromMinutes(random.NextDouble() * 1000.0), + random.Next(100)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random GroupTagConstraint. + public static GroupTagConstraint RandomGroupTagConstraint(Random random, int maxDepth) => + new GroupTagConstraint( + RandomGroupConstraint(random, maxDepth - 1), + RandomDicomTagIndex(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random OrderedDateTimeConstraint. + public static OrderedDateTimeConstraint RandomOrderedDateTimeConstraint(Random random, int maxDepth) => + new OrderedDateTimeConstraint( + RandomDicomTagIndex(random), + RandomDicomOrderedTagDateTime(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random OrderedDoubleConstraint. + public static OrderedDoubleConstraint RandomOrderedDoubleConstraint(Random random, int maxDepth) => + new OrderedDoubleConstraint( + RandomDicomTagIndex(random), + RandomDicomOrderedTagDouble(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random OrderedIntConstraint. + public static OrderedIntConstraint RandomOrderedIntConstraint(Random random, int maxDepth) => + new OrderedIntConstraint( + RandomDicomTagIndex(random), + RandomDicomOrderedTagInt(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random OrderedStringConstraint. + public static OrderedStringConstraint RandomOrderedStringConstraint(Random random, int maxDepth) => + new OrderedStringConstraint( + RandomDicomTagIndex(random), + RandomDicomOrderedTagString(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random RegexConstraint. + public static RegexConstraint RandomRegexConstraint(Random random, int maxDepth) => + new RegexConstraint( + RandomDicomTagIndex(random), + RandomString(random, 18), + RandomEnum(random), + random.Next(100)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random RequiredTagConstraint. + public static RequiredTagConstraint RandomRequiredTagConstraint(Random random, int maxDepth) => + new RequiredTagConstraint( + RandomEnum(random), + RandomDicomTagConstraint(random, maxDepth)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random StringContainsConstraint. + public static StringContainsConstraint RandomStringContainsConstraint(Random random, int maxDepth) => + new StringContainsConstraint( + RandomDicomTagIndex(random), + RandomString(random, 5), + random.Next(100)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random TimeOrderConstraint. + public static TimeOrderConstraint RandomTimeOrderConstraint(Random random, int maxDepth) => + new TimeOrderConstraint( + RandomDicomTagIndex(random), + RandomDicomOrderedTagTimeSpan(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random UIDStringOrderConstraint. + public static UIDStringOrderConstraint RandomUIDStringOrderConstraint(Random random, int maxDepth) => + new UIDStringOrderConstraint( + RandomDicomTagIndex(random), + RandomDicomOrderedTagString(random)); + + /// + /// DicomConstraint generators, excluding RandomGroupTagConstraint. + /// + /// + /// Exclude RandomGroupTagConstraint to prevent stack overflow. + /// + public static readonly Func[] RandomDicomConstraintGenerators = + { + RandomOrderedDateTimeConstraint, + RandomOrderedDoubleConstraint, + RandomOrderedIntConstraint, + RandomOrderedStringConstraint, + RandomRegexConstraint, + RandomRequiredTagConstraint, + RandomStringContainsConstraint, + RandomTimeOrderConstraint, + RandomUIDStringOrderConstraint, + }; + + /// + /// DicomConstraint generators, including RandomGroupTagConstraint. + /// + public static readonly Func[] RandomDicomConstraintGeneratorsWithGroup = + RandomDicomConstraintGenerators.Concat(new Func[] { RandomGroupTagConstraint }).ToArray(); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random DicomConstraint. + public static DicomConstraint RandomDicomConstraint(Random random, int maxDepth) => + RandomItem(random, maxDepth, maxDepth > 0 ? RandomDicomConstraintGeneratorsWithGroup : RandomDicomConstraintGenerators); + + /// + /// DicomTagConstraint generators, excluding RandomGroupTagConstraint. + /// + /// + /// Exclude RandomGroupTagConstraint to prevent stack overflow. + /// + public static readonly Func[] RandomDicomTagConstraintGenerators = + { + RandomOrderedDateTimeConstraint, + RandomOrderedDoubleConstraint, + RandomOrderedIntConstraint, + RandomOrderedStringConstraint, + RandomRegexConstraint, + RandomStringContainsConstraint, + RandomTimeOrderConstraint, + RandomUIDStringOrderConstraint, + }; + + /// + /// DicomTagConstraint generators, including RandomGroupTagConstraint. + /// + public static readonly Func[] RandomDicomTagConstraintGeneratorsWithGroup = + RandomDicomTagConstraintGenerators.Concat(new Func[] { RandomGroupTagConstraint }).ToArray(); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random DicomTagConstraint. + public static DicomTagConstraint RandomDicomTagConstraint(Random random, int maxDepth) => + RandomItem(random, maxDepth, maxDepth > 0 ? RandomDicomTagConstraintGeneratorsWithGroup : RandomDicomTagConstraintGenerators); + + /// + /// Generate a random array, possibly empty, of . + /// + /// Random. + /// Limit nesting on group tags. + /// Random array of DicomConstraints. + public static DicomConstraint[] RandomDicomConstraints(Random random, int maxDepth) => + RandomBool(random) ? RandomArray(random, maxDepth, 20, RandomDicomConstraint) : Array.Empty(); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random GroupConstraint. + public static GroupConstraint RandomGroupConstraint(Random random, int maxDepth) => + new GroupConstraint( + RandomDicomConstraints(random, maxDepth), + RandomEnum(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random ModelChannelConstraints. + public static ModelChannelConstraints RandomModelChannelConstraint(Random random, int maxDepth) => + new ModelChannelConstraints( + RandomString(random), + RandomGroupConstraint(random, maxDepth), + RandomGroupConstraint(random, maxDepth), + random.Next(2, 5), + random.Next(5, 9)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random TagReplacement. + public static TagReplacement RandomTagReplacement(Random random, int maxDepth) => + new TagReplacement( + RandomEnum(random), + RandomDicomTagIndex(random), + RandomString(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random ModelConstraintsConfig. + public static ModelConstraintsConfig RandomModelConstraintsConfig(Random random, int maxDepth) => + new ModelConstraintsConfig( + RandomString(random, 9), + RandomArray(random, maxDepth, 4, RandomModelChannelConstraint), + RandomArray(random, maxDepth, 7, RandomTagReplacement)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random AETConfig. + public static AETConfig RandomAETConfig(Random random, int maxDepth) + { + var aetConfigType = RandomEnum(random); + var modelsConfig = AETConfig.NeedsModelConfig(aetConfigType) ? RandomArray(random, maxDepth, 5, RandomModelConstraintsConfig) : null; + + return new AETConfig( + aetConfigType, + modelsConfig); + } + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random ClientAETConfig. + public static ClientAETConfig RandomClientAETConfig(Random random, int maxDepth) => + new ClientAETConfig( + RandomAETConfig(random, maxDepth), + RandomDicomEndPoint(random), + RandomBool(random)); + + /// + /// Generate random . + /// + /// Random. + /// Limit nesting on group tags. + /// Random AETConfigModel. + public static AETConfigModel RandomAETConfigModel(Random random, int maxDepth) => + new AETConfigModel( + RandomString(random, 10), + RandomString(random, 11), + RandomClientAETConfig(random, maxDepth)); + + [TestCategory("ConfigurationProvider")] + [Description("Creates a random gateway receive config, saves it, and checks it loads correctly.")] + [TestMethod] + public void TestLoadGatewayReceiveConfig() + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var expectedGatewayReceiveConfig = RandomGatewayReceiveConfig(random); + Serialise(expectedGatewayReceiveConfig, configurationDirectory, GatewayReceiveConfigProvider.GatewayReceiveConfigFileName); + + var gatewayReceiveConfigProvider = new GatewayReceiveConfigProvider(_baseTestLogger, configurationDirectory); + var actualGatewayReceiveConfig = gatewayReceiveConfigProvider.GatewayReceiveConfig(); + + Assert.AreEqual(expectedGatewayReceiveConfig, actualGatewayReceiveConfig); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates a random gateway processor config, saves it, and checks it loads correctly.")] + [TestMethod] + public void TestLoadGatewayProcessorConfig() + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var expectedGatewayProcessorConfig = RandomGatewayProcessorConfig(random); + Serialise(expectedGatewayProcessorConfig, configurationDirectory, GatewayProcessorConfigProvider.GatewayProcessorConfigFileName); + + var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider(_baseTestLogger, configurationDirectory); + var actualGatewayProcessorConfig = gatewayProcessorConfigProvider.GatewayProcessorConfig(); + + Assert.AreEqual(expectedGatewayProcessorConfig, actualGatewayProcessorConfig); + } + + /// + /// Create a list of random AET config models, save them to a single file, and check they load correctly. + /// + /// True to use AETConfigProvider in single file mode, false to use it in folder mode. + public void TestLoadAETConfigCommon(bool useFile) + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var expectedAETConfigModels = RandomArray(random, 2, 10, RandomAETConfigModel); + var folder = string.Empty; + var filename = string.Empty; + + if (useFile) + { + folder = configurationDirectory; + filename = AETConfigProvider.AETConfigFileName; + } + else + { + folder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName); + Directory.CreateDirectory(folder); + filename = "test1.json"; + } + + Serialise(expectedAETConfigModels, folder, filename); + + var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory, useFile); + var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray(); + + Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels)); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates a list of AET config models, saves it to a file, and checks it loads correctly.")] + [TestMethod] + public void TestLoadAETConfigFile() + { + TestLoadAETConfigCommon(true); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates a list of AET config models, saves it to a file in a folder, and checks it loads correctly.")] + [TestMethod] + public void TestLoadAETConfigFolder() + { + TestLoadAETConfigCommon(false); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates a list of AET config models, saves them along with two other config files, and checks the models load correctly" + + "and the other configs are ignored.")] + [TestMethod] + public void TestLoadAETConfigInvalidFiles() + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var aetConfigFolder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName); + Directory.CreateDirectory(aetConfigFolder); + + var expectedAETConfigModels = RandomArray(random, 3, 10, RandomAETConfigModel); + Serialise(expectedAETConfigModels, aetConfigFolder, "test1.json"); + + // Write a random GatewayProcessorConfig + var gatewayProcessorConfig = RandomGatewayProcessorConfig(random); + Serialise(gatewayProcessorConfig, aetConfigFolder, "test2.json"); + + // Write a random GatewayReceiverConfig + var gatewayReceiveConfig = RandomGatewayReceiveConfig(random); + Serialise(gatewayReceiveConfig, aetConfigFolder, "test3.json"); + + var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory); + var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray(); + + Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels)); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates a list of AET config models, saves them to one file per called/calling pair, and checks they all load correctly.")] + [TestMethod] + public void TestLoadAETConfigConcatenate() + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var aetConfigFolder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName); + Directory.CreateDirectory(aetConfigFolder); + + var expectedAETConfigModels = RandomArray(random, 3, 10, RandomAETConfigModel); + for (var i = 0; i < expectedAETConfigModels.Length; i++) + { + var expectedAETConfig = new[] { expectedAETConfigModels[i] }; + Serialise(expectedAETConfig, aetConfigFolder, string.Format("GatewayModelRulesConfig{0}.json", i)); + } + + var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory); + var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray(); + + Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels)); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates a single AET config model, splits it at the model config point, saves each to a file, and checks they all load correctly.")] + [TestMethod] + public void TestLoadAETConfigMerge() + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var aetConfigFolder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName); + Directory.CreateDirectory(aetConfigFolder); + + // This test needs ModelConfigs, but the AETConfigType will be one of: Model, ModelDryRun, ModelWithResultDryRun + // just loop until it has the right type. + var expectedAETConfigModels = RandomArray(random, 3, 1, RandomAETConfigModel); + while (expectedAETConfigModels[0].AETConfig.Config.ModelsConfig == null || + expectedAETConfigModels[0].AETConfig.Config.ModelsConfig.Length == 0) + { + expectedAETConfigModels = RandomArray(random, 3, 1, RandomAETConfigModel); + } + + for (var i = 0; i < expectedAETConfigModels[0].AETConfig.Config.ModelsConfig.Length; i++) + { + // Clone the expected AET config model taking only the ith models config. + var expectedAETConfig0 = expectedAETConfigModels[0].With( + aetConfig: expectedAETConfigModels[0].AETConfig.With( + config: expectedAETConfigModels[0].AETConfig.Config.With( + modelsConfig: new[] { expectedAETConfigModels[0].AETConfig.Config.ModelsConfig[i] }))); + + var expectedAETConfig = new[] { expectedAETConfig0 }; + Serialise(expectedAETConfig, aetConfigFolder, string.Format("GatewayModelRulesConfig{0}.json", i), true); + } + + var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory); + var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray(); + + Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels)); + } + + [TestCategory("ConfigurationProvider")] + [Description("Creates an AET config, splits it the base and at the model config point, saves each into a separate file, and check they all load correctly.")] + [TestMethod] + public void TestLoadAETConfigSplitAndMerge() + { + var configurationDirectory = CreateTemporaryDirectory().FullName; + var random = new Random(); + + var aetConfigFolder = Path.Combine(configurationDirectory, AETConfigProvider.AETConfigFolderName); + Directory.CreateDirectory(aetConfigFolder); + + var expectedAETConfigModels = RandomArray(random, 3, 10, RandomAETConfigModel); + + for (var j = 0; j < expectedAETConfigModels.Length; j++) + { + if (expectedAETConfigModels[j].AETConfig.Config.ModelsConfig != null) + { + // If this model has ModelsConfig then create a new AET config model for each of the + // models config. + for (var i = 0; i < expectedAETConfigModels[j].AETConfig.Config.ModelsConfig.Length; i++) + { + // Clone the expected AET config model taking only the ith models config. + var expectedAETConfig0 = expectedAETConfigModels[j].With( + aetConfig: expectedAETConfigModels[j].AETConfig.With( + config: expectedAETConfigModels[j].AETConfig.Config.With( + modelsConfig: new[] { expectedAETConfigModels[j].AETConfig.Config.ModelsConfig[i] }))); + + var expectedAETConfig = new[] { expectedAETConfig0 }; + Serialise(expectedAETConfig, aetConfigFolder, string.Format("GatewayModelRulesConfig{0}_{1}.json", j, i), true); + } + } + else + { + // No ModelsConfig so just clone at the base. + var expectedAETConfig = new[] { expectedAETConfigModels[j] }; + Serialise(expectedAETConfig, aetConfigFolder, string.Format("GatewayModelRulesConfig{0}.json", j), true); + } + } + + var aetConfigProvider = new AETConfigProvider(_baseTestLogger, configurationDirectory); + var actualAETConfigModels = aetConfigProvider.GetAETConfigs().ToArray(); + + Assert.IsTrue(expectedAETConfigModels.SequenceEqual(actualAETConfigModels)); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DebugTextWriter.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DebugTextWriter.cs new file mode 100644 index 0000000..524600a --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DebugTextWriter.cs @@ -0,0 +1,44 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.IO; + +namespace Microsoft.InnerEye.Listener.Tests.DataProviderTests +{ + /// + /// A TextWriter wrapper for Debug interface of Visual Studio so that we could output fo-dicom log events there. + /// + public class DebugTextWriter : StreamWriter + { + public DebugTextWriter() + : base(new DebugOutStream(), Encoding.Unicode, 1024) + { + this.AutoFlush = true; + } + + private sealed class DebugOutStream : Stream + { + public override void Write(byte[] buffer, int offset, int count) + { + Debug.Write(Encoding.Unicode.GetString(buffer, offset, count)); + } + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override void Flush() => Debug.Flush(); + + public override long Length => throw bad_op; + public override int Read(byte[] buffer, int offset, int count) => throw bad_op; + public override long Seek(long offset, SeekOrigin origin) => throw bad_op; + public override void SetLength(long value) => throw bad_op; + public override long Position + { + get => throw bad_op; + set => throw bad_op; + } + + private static InvalidOperationException bad_op => new InvalidOperationException(); + }; + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataReceiverTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataReceiverTests.cs new file mode 100644 index 0000000..2643e7e --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataReceiverTests.cs @@ -0,0 +1,150 @@ +namespace Microsoft.InnerEye.Listener.Tests.DataProviderTests +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + using Dicom.Network; + + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.DataProvider.Models; + using Microsoft.InnerEye.Listener.Tests.Common.Helpers; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DicomDataReceiverTests : BaseTestClass + { + [Timeout(60 * 1000)] + [TestCategory("DicomDataReceiver")] + [Description("Starts two data receivers listening on the same port.")] + [TestMethod] + public void DicomDataReceiverSamePort() + { + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 120, "127.0.0.1"); + var resultsDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver1 = new ListenerDataReceiver(new ListenerDicomSaver(resultsDirectory.FullName))) + { + Assert.IsTrue(dicomDataReceiver1.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1))); + + using (var dicomDataReceiver2 = new ListenerDataReceiver(new ListenerDicomSaver(resultsDirectory.FullName))) + { + Assert.ThrowsException(() => dicomDataReceiver2.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1))); + + // Check you can start again and on a different port with the same AE title and IP address. + Assert.IsTrue(dicomDataReceiver2.StartServer(applicationEntity.Port + 1, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1))); + } + } + } + + [Timeout(600 * 1000)] + [TestCategory("DicomDataReceiverDCMTK")] + [Description("Starts the listener and pushes a file over Dicom to check we can receive data.")] + [TestMethod] + public async Task DicomDataReceiverServerStarts() + { + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 191, "127.0.0.1"); + var resultsDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultsDirectory.FullName))) + { + var result = dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1)); + + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + Assert.IsTrue(result); + Assert.IsTrue(dicomDataReceiver.IsListening); + + Assert.ThrowsException(() => dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1))); + + var dataSender = new DicomDataSender(); + var echoResult = await dataSender.DicomEchoAsync( + "RListener", + applicationEntity.Title, + applicationEntity.Port, + applicationEntity.IpAddress); + + // Check echo + Assert.IsTrue(echoResult == DicomOperationResult.Success); + + DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + applicationEntity.Port, + ScuProfile.LEExplicitCT, + TestContext); + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromSeconds(10)); + + // Check the file exists + Assert.IsTrue(File.Exists(Path.Combine(folderPath, @"1.2.840.113619.2.81.290.1.36662.3.1.20151027.220159.dcm"))); + + dicomDataReceiver.StopServer(); + } + } + + [Ignore("This test works locally but is not robust on the build system. Mark as ignore for now.")] + [Timeout(60 * 1000)] + [TestCategory("DicomDataReceiver")] + [Description("Starts the listener and sends 10 associations to the receiver. Checks all are received correctly.")] + [TestMethod] + public void DicomDataReceiverMutlipleAssociations() + { + const int numberOfAssociations = 10; + const int port = 122; + + var applicationEntity = new GatewayApplicationEntity("RListenerTest", port, "127.0.0.1"); + var resultsDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultsDirectory.FullName))) + { + var result = dicomDataReceiver.StartServer( + applicationEntity.Port, + BuildAcceptedSopClassesAndTransferSyntaxes, + TimeSpan.FromSeconds(1)); + + var associationsReceivedCount = 0; + var receivedAssociations = new string[numberOfAssociations]; + + dicomDataReceiver.DataReceived += (sender, e) => + { + if (e.ProgressCode == DicomReceiveProgressCode.AssociationEstablished) + { + receivedAssociations[int.Parse(e.DicomAssociation.CallingAE)] = e.DicomAssociation.CallingAE; + Interlocked.Increment(ref associationsReceivedCount); + } + }; + + Assert.IsTrue(result); + Assert.IsTrue(dicomDataReceiver.IsListening); + + Parallel.For(0, numberOfAssociations, i => + { + var dcmtkResult = DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + port, + ScuProfile.LEExplicitCT, + TestContext, + waitForExit: false, + applicationEntityTitle: i.ToString()); + }); + + SpinWait.SpinUntil(() => associationsReceivedCount == numberOfAssociations, TimeSpan.FromMinutes(1)); + + for (var i = 0; i < numberOfAssociations; i++) + { + Assert.IsTrue(receivedAssociations[i] == i.ToString()); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataSenderTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataSenderTests.cs new file mode 100644 index 0000000..60840bb --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/DataProviderTests/DicomDataSenderTests.cs @@ -0,0 +1,114 @@ +namespace Microsoft.InnerEye.Listener.Tests.DataProviderTests +{ + using System; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.DataProvider.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DicomDataSenderTests : BaseTestClass + { + [Timeout(60 * 1000)] + [TestCategory("DicomDataSender")] + [Description("Sends a Dicom echo from the data sender and checks we get a valid response.")] + [TestMethod] + public async Task DicomEchoTest() + { + var dataSender = new DicomDataSender(); + + var applicationEntity = new GatewayApplicationEntity( + title: "RListenerTest", + port: new Random().Next(130, ApplicationEntityValidationHelpers.MaximumPortNumber), + ipAddress: "127.0.0.1"); + + var resultsDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultsDirectory.FullName))) + { + var started = dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(10)); + + Assert.IsTrue(started); + + var result1 = await dataSender.DicomEchoAsync( + "Hello", + applicationEntity.Title, + applicationEntity.Port, + applicationEntity.IpAddress); + + Assert.AreEqual(DicomOperationResult.Success, result1); + + dicomDataReceiver.StopServer(); + } + + var result2 = await dataSender.DicomEchoAsync( + "Hello", + applicationEntity.Title, + applicationEntity.Port, + applicationEntity.IpAddress); + + Assert.AreEqual(DicomOperationResult.NoResponse, result2); + + // Try ping with IPv6 Address + var result3 = await dataSender.DicomEchoAsync("Hello", "RListenerTest", 105, "2a00:1450:4009:800::200e"); + + Assert.AreEqual(DicomOperationResult.NoResponse, result3); + } + + [Timeout(60 * 1000)] + [TestCategory("DicomDataSender")] + [Description("Sends a files over Dicom from the data sender.")] + [TestMethod] + public async Task DicomSendFilesTest() + { + var dataSender = new DicomDataSender(); + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 131, "127.0.0.1"); + + var dicomFiles = new DirectoryInfo(@"Images\1ValidSmall\").GetFiles().Select(x => DicomFile.Open(x.FullName)).ToArray(); + var rtFile = await DicomFile.OpenAsync(@"Images\LargeSeriesWithContour\rtstruct.dcm"); + + var resultsDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultsDirectory.FullName))) + { + dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1)); + + var results = await dataSender.SendFilesAsync( + "Hello", + applicationEntity.Title, + applicationEntity.Port, + applicationEntity.IpAddress, + dicomFiles); + + foreach (var result in results) + { + // Check we can send non-RT files + Assert.AreEqual(DicomOperationResult.Success, result.Item2); + } + + var rtResult = await dataSender.SendFilesAsync( + "Hello", + applicationEntity.Title, + applicationEntity.Port, + applicationEntity.IpAddress, + rtFile); + + Assert.AreEqual(1, rtResult.Count()); + + foreach (var result in rtResult) + { + // Check we can send RT files + Assert.AreEqual(DicomOperationResult.Success, result.Item2); + } + + dicomDataReceiver.StopServer(); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ExtensionTests/DicomFileExtensionTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ExtensionTests/DicomFileExtensionTests.cs new file mode 100644 index 0000000..32341a7 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ExtensionTests/DicomFileExtensionTests.cs @@ -0,0 +1,105 @@ +namespace Microsoft.InnerEye.Listener.Tests +{ + using System; + using System.IO; + using System.Linq; + + using Dicom; + + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DicomFileExtensionTests : BaseTestClass + { + [TestCategory("DicomFileExtensions")] + [Description("Tests that we can create a copy of a Dicom file without the image pixel data.")] + [TestMethod] + public void TestRemovePixelData() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var imagePath = @"Images\1ValidSmall\1.dcm"; + var dicomFile = DicomFile.Open(imagePath); + var metadataBytes = dicomFile.CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + var bytesOriginalFile = File.ReadAllBytes(imagePath); + + // Check the metadata is less than 1kb + Assert.IsTrue(metadataBytes.Length < 1120); + Assert.IsTrue(metadataBytes.Length > 0); + } + + [TestCategory("DicomFileExtensions")] + [Description("Tests that we can create a copy of a Dicom file without the image pixel data if some of the tags are invalid")] + [TestMethod] + public void TestRemovePixelDataInvalidTags() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var imagePath = @"Images\InvalidPN\1.dcm"; + var dicomFile = DicomFile.Open(imagePath); + var metadataBytes = dicomFile.CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + var pixelData = Dicom.Imaging.DicomPixelData.Create(dicomFile.Dataset); + byte[] org_data = pixelData.GetFrame(0).Data; + + // Pixel data exists and is non-empty in the original file + Assert.IsTrue(org_data.Length > 1); + + var ds = DicomFile.Open(new MemoryStream(metadataBytes)).Dataset; + + // Pixel data is missing in modified file + Assert.ThrowsException(() => { pixelData = Dicom.Imaging.DicomPixelData.Create(ds); }); + + // Check the metadata is less than 1.2kb + Assert.IsTrue(metadataBytes.Length < 1200); + Assert.IsTrue(metadataBytes.Length > 0); + } + + [TestCategory("DicomFileExtensions")] + [Description("Tests we can enqueue a 4000 slice DICOM file onto the message queue")] + [TestMethod] + public void TestQueueDataLimit() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var dicomFile = DicomFile.Open(@"Images\1ValidSmall\1.dcm"); + var files = new byte[3600][]; + + for (var i = 0; i < files.Length; i++) + { + files[i] = dicomFile.CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + } + + using (var queue = GetTestMessageQueue()) + { + TransactionalEnqueue( + queue, + new DownloadQueueItem( + segmentationId: Guid.Empty.ToString(), + modelId: Guid.Empty.ToString(), + resultsDirectory: CreateTemporaryDirectory().FullName, + referenceDicomFiles: files, + calledApplicationEntityTitle: "TEST1", + callingApplicationEntityTitle: "TEST2", + destinationApplicationEntity: new GatewayApplicationEntity("TestTitle", 140, "127.0.0.1"), + tagReplacementJsonString: "", + associationGuid: Guid.Empty, + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + var result = TransactionalDequeue(queue).ReferenceDicomFiles.ToArray(); + + for (var i = 0; i < files.Length; i++) + { + for (var ii = 0; ii < files[i].Length; ii++) + { + Assert.AreEqual(files[i][ii], result[i][ii]); + } + } + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/LoggingTests/GatewayLoggingTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/LoggingTests/GatewayLoggingTests.cs new file mode 100644 index 0000000..0c68b34 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/LoggingTests/GatewayLoggingTests.cs @@ -0,0 +1,19 @@ +namespace Microsoft.InnerEye.Listener.Tests.LoggingTests +{ + using System; + using System.Diagnostics; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class GatewayLoggingTests : BaseTestClass + { + [Timeout(60 * 1000)] + [TestCategory("LoggingTests")] + [Description("Tests sending a trace event with random characters does not throw exceptions.")] + [TestMethod] + public void TestTraceLogging1() + { + Trace.TraceError($"[{GetType().Name}] #$^&*(^Funky {new Exception("Something went really wrong.")}"); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/QueueItemQueueTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/QueueItemQueueTests.cs new file mode 100644 index 0000000..dda2b9c --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/QueueItemQueueTests.cs @@ -0,0 +1,195 @@ +namespace Microsoft.InnerEye.Listener.Tests.MessageQueueTests +{ + using System; + using System.Linq; + + using Microsoft.InnerEye.Gateway.Models; + + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class QueueItemQueueTests : BaseTestClass + { + [TestCategory("QueueItem")] + [Timeout(60 * 1000)] + [Description("Tests that the delete queue item can be added to the message queue and read correctly.")] + [TestMethod] + public void TestDeleteQueueItem() + { + // Get the receive queue + using (var queue = GetUniqueMessageQueue()) + { + var expected = new DeleteQueueItem( + new AssociationQueueItemBase( + "HelloWorld1", + "HelloWorld2", + Guid.NewGuid(), + DateTime.UtcNow, + 5), + @"c:\sdgfsd", + @"arandompath", + @"d:\temp\dicomfile.dcm") + { + DequeueCount = 5 + }; + + TransactionalEnqueue(queue, expected); + + var actual = TransactionalDequeue(queue); + + Assert.IsNotNull(actual); + + Assert.AreEqual(expected.AssociationGuid, actual.AssociationGuid); + Assert.AreEqual(expected.AssociationDateTime, actual.AssociationDateTime); + Assert.AreEqual(expected.CalledApplicationEntityTitle, actual.CalledApplicationEntityTitle); + Assert.AreEqual(expected.CallingApplicationEntityTitle, actual.CallingApplicationEntityTitle); + Assert.AreEqual(expected.DequeueCount, actual.DequeueCount); + Assert.AreEqual(expected.Paths.Count(), actual.Paths.Count()); + + for (var i = 0; i < expected.Paths.Count(); i++) + { + Assert.AreEqual(expected.Paths.ElementAt(i), actual.Paths.ElementAt(i)); + } + } + } + + [TestCategory("QueueItem")] + [Timeout(60 * 1000)] + [Description("Tests that the push queue item can be added to the message queue and read correctly.")] + [TestMethod] + public void TestPushQueueItem() + { + // Get the receive queue + using (var queue = GetUniqueMessageQueue()) + { + var queueItem = new PushQueueItem( + destinationApplicationEntity: new GatewayApplicationEntity("Test3", 105, "Test4"), + calledApplicationEntityTitle: "TestAet", + callingApplicationEntityTitle: "Test6", + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + filePaths: new[] { + @"c:\sdgfsd", + @"arandompath", + @"d:\temp\dicomfile.dcm" + }) + { + DequeueCount = 3 + }; + + TransactionalEnqueue(queue, queueItem); + + var item = TransactionalDequeue(queue); + + Assert.IsNotNull(item); + + AssertAllProperties(queueItem, item); + } + } + + [TestCategory("QueueItem")] + [Timeout(60 * 1000)] + [Description("Tests that the download queue item can be added to the message queue and read correctly.")] + [TestMethod] + public void TestDownloadQueueItem() + { + // Get the receive queue + using (var queue = GetUniqueMessageQueue()) + { + var queueItem = new DownloadQueueItem( + segmentationId: Guid.NewGuid().ToString(), + modelId: Guid.NewGuid().ToString(), + resultsDirectory: CreateTemporaryDirectory().FullName, + referenceDicomFiles: new[] { new byte[] { 5, 6, 8, 10 }, new byte[] { 2, 9, 11, 22 } }, + calledApplicationEntityTitle: "Test2", + callingApplicationEntityTitle: "Test30", + destinationApplicationEntity: new GatewayApplicationEntity("Test3", 105, "Test4"), + tagReplacementJsonString: "HELLO WORLD 1 / 2 3; 5", + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false) + { + DequeueCount = 4 + }; + + TransactionalEnqueue(queue, queueItem); + + var item = TransactionalDequeue(queue); + + Assert.IsNotNull(item); + + AssertAllProperties(queueItem, item); + } + } + + [TestCategory("QueueItem")] + [Timeout(60 * 1000)] + [Description("Tests that the upload queue item can be added to the message queue and read correctly.")] + [TestMethod] + public void TestUploadQueueItem() + { + // Get the receive queue + using (var queue = GetUniqueMessageQueue()) + { + var queueItem = new UploadQueueItem( + calledApplicationEntityTitle: "Test1", + callingApplicationEntityTitle: "Test2", + associationFolderPath: "Test4", + rootDicomFolderPath: "Test6", + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow) + { + DequeueCount = 9 + }; + + TransactionalEnqueue(queue, queueItem); + + var item = TransactionalDequeue(queue); + + Assert.IsNotNull(item); + + AssertAllProperties(queueItem, item); + } + } + + private void AssertAllProperties(T expectedValue, T actualValue) + { + const string SystemNamespace = "System"; + + var expectedType = expectedValue.GetType(); + + if (expectedType.Namespace == SystemNamespace && !expectedType.IsArray) + { + Assert.AreEqual(expectedValue, actualValue); + return; + } + + foreach (var property in expectedType.GetProperties().Where(x => x.GetIndexParameters().Length == 0)) + { + var expected = property.GetValue(expectedValue); + var actual = property.GetValue(actualValue); + + var type = expected.GetType(); + + if (type.IsArray) + { + var expectedItem = expected as Array; + var actualItem = actual as Array; + + for (var i = 0; i < expectedItem.Length; i++) + { + AssertAllProperties(expectedItem.GetValue(i), actualItem.GetValue(i)); + } + } + else if (type.Namespace == SystemNamespace) + { + Assert.AreEqual(expected, actual); + } + else + { + AssertAllProperties(expected, actual); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/SQLiteMessageQueueTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/SQLiteMessageQueueTests.cs new file mode 100644 index 0000000..4f5cc25 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/MessageQueueTests/SQLiteMessageQueueTests.cs @@ -0,0 +1,678 @@ +namespace Microsoft.InnerEye.Listener.Tests.MessageQueueTests +{ + using System; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.MessageQueueing.Sqlite; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SQLiteTests : BaseTestClass + { + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests the SQLite transaciton throws an exception when committing or aborting before begin is called.")] + [TestMethod] + public void SqliteTransactionExceptionTests1() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + const uint transactionLeaseMs = 3000; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath, transactionLeaseMs: transactionLeaseMs)) + { + messageQueue.Clear(); + + using (var queueTransaction1 = messageQueue.CreateQueueTransaction()) + { + Assert.ThrowsException(() => queueTransaction1.Commit()); + Assert.ThrowsException(() => queueTransaction1.Abort()); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests the SQLite transaciton throws an exception when committing or aborting when it is disposed.")] + [TestMethod] + public void SqliteTransactionExceptionTests2() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + const uint transactionLeaseMs = 3000; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath, transactionLeaseMs: transactionLeaseMs)) + { + var queueTransaction1 = messageQueue.CreateQueueTransaction(); + queueTransaction1.Dispose(); + + Assert.ThrowsException(() => queueTransaction1.Begin()); + Assert.ThrowsException(() => queueTransaction1.Commit()); + Assert.ThrowsException(() => queueTransaction1.Abort()); + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests adding an item on the queue and waits for past the time the lease should have expired. Checks the renew lease code is working.")] + [TestMethod] + public async Task SqliteRenewLeaseTests1() + { + string messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + const uint transactionLeaseMs = 1000; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath, transactionLeaseMs: transactionLeaseMs)) + { + messageQueue.Clear(); + + using (var queueTransaction1 = messageQueue.CreateQueueTransaction()) + { + var expected = CreateRandomQueueItem(); + + queueTransaction1.Begin(); + + messageQueue.Enqueue(expected, queueTransaction1); + + // Dequeue the item to start the renew lease expiry + var dequeued = messageQueue.DequeueNextMessage(queueTransaction1); + + // Wait for twice the time - the lease should have expired if the renew code is not working + await Task.Delay(TimeSpan.FromMilliseconds(transactionLeaseMs * 2)); + + Assert.ThrowsException(() => messageQueue.DequeueNextMessage(queueTransaction1)); + } + + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + Assert.ThrowsException(() => messageQueue.DequeueNextMessage(queueTransaction)); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests adding an item on the queue and waits for past the time the lease should have expired. Checks the item can be recovered on a different transaction.")] + [TestMethod] + public async Task SqliteExpiredLeaseTests1() + { + string messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + const uint transactionLeaseMs = 1000; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath, transactionLeaseMs: transactionLeaseMs)) + { + messageQueue.Clear(); + + var expected = CreateRandomQueueItem(); + + TransactionalEnqueue(messageQueue, expected); + + // Create a new transaction with the renew task larger than the time of the transaction lease (not a good idea in practice) + using (var queueTransaction1 = new SqliteMessageQueueTransaction(messageQueue.DatabaseConnectionString, 60 * 1000)) + { + queueTransaction1.Begin(); + + // Dequeue the item to start the renew lease expiry + var actual1 = messageQueue.DequeueNextMessage(queueTransaction1); + + AssertCompare(expected, actual1); + + // Wait for twice the time - the lease should have now expired + await Task.Delay(TimeSpan.FromMilliseconds(transactionLeaseMs * 2)); + + // Dequeue on a different transaction + var actual2 = TransactionalDequeue(messageQueue); + + // Check the item was recovered. + AssertCompare(expected, actual2); + + queueTransaction1.Abort(); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests adding an item onto the queue. Dequeue and clear the entire queue whilst the transaction is open. Test Abort and Commit does not throw exceptions.")] + [TestMethod] + public async Task SqliteClearDuringTransaction1() + { + string messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + const uint transactionLeaseMs = 1000; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath, transactionLeaseMs: transactionLeaseMs)) + { + messageQueue.Clear(); + + var expected = CreateRandomQueueItem(); + + // Also test Abort is called when disposing + using (var transaction = messageQueue.CreateQueueTransaction()) + { + transaction.Begin(); + + messageQueue.Enqueue(expected, transaction); + var actual = messageQueue.DequeueNextMessage(transaction); + + messageQueue.Clear(); + + AssertCompare(expected, actual); + + // Wait for the renew lease task to be called at least once. + await Task.Delay(TimeSpan.FromMilliseconds(transactionLeaseMs * 2)); + } + + using (var transaction = messageQueue.CreateQueueTransaction()) + { + transaction.Begin(); + + messageQueue.Enqueue(expected, transaction); + var actual = messageQueue.DequeueNextMessage(transaction); + + messageQueue.Clear(); + + AssertCompare(expected, actual); + + // Wait for the renew lease task to be called at least once. + await Task.Delay(TimeSpan.FromMilliseconds(transactionLeaseMs * 2)); + + // Make sure no exception is thrown. + transaction.Commit(); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests that we can enqueue and dequeue in the same transaction without commiting .")] + [TestMethod] + public void SqliteTransactionTests1() + { + string messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + + using (var queueTransaction1 = messageQueue.CreateQueueTransaction()) + { + var expected = CreateRandomQueueItem(); + + queueTransaction1.Begin(); + + messageQueue.Enqueue(expected, queueTransaction1); + + var actual = messageQueue.DequeueNextMessage(queueTransaction1); + + AssertCompare(expected, actual); + + Assert.ThrowsException(() => messageQueue.DequeueNextMessage(queueTransaction1)); + + queueTransaction1.Commit(); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests that we can enqueue and attempt to dequeue before a commit in a different transaction.")] + [TestMethod] + public void SqliteTransactionTests2() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + + using (var queueTransaction1 = messageQueue.CreateQueueTransaction()) + { + var expected = CreateRandomQueueItem(); + + queueTransaction1.Begin(); + + messageQueue.Enqueue(expected, queueTransaction1); + + using (var queueTransaction2 = messageQueue.CreateQueueTransaction()) + { + queueTransaction2.Begin(); + + Assert.ThrowsException(() => messageQueue.DequeueNextMessage(queueTransaction2)); + } + + queueTransaction1.Abort(); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests that abort is called when a transaction is disposed.")] + [TestMethod] + public void SqliteTransactionDisposeTest1() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + var expected = CreateRandomQueueItem(); + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + + // Enqueue the expected item + TransactionalEnqueue(messageQueue, expected); + + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + // Dequeue but don't call abort specifically - let the dispose method do this + var actual = messageQueue.DequeueNextMessage(queueTransaction); + + AssertCompare(expected, actual); + } + + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + var actual = messageQueue.DequeueNextMessage(queueTransaction); + + AssertCompare(expected, actual); + + queueTransaction.Commit(); + } + } + } + + [Timeout(60 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests the Sqlite Message Queue supports nested transactions.")] + [TestMethod] + public void SqliteNestedTransactionTest() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + + using (var queueTransaction1 = messageQueue.CreateQueueTransaction()) + { + queueTransaction1.Begin(); + + using (var queueTransaction2 = messageQueue.CreateQueueTransaction()) + { + queueTransaction2.Begin(); + messageQueue.Enqueue(CreateRandomQueueItem(0), queueTransaction2); + queueTransaction2.Commit(); + } + + var queueItem = messageQueue.DequeueNextMessage(queueTransaction1); + Assert.IsNotNull(queueItem); + + queueTransaction1.Commit(); + } + } + } + + [Timeout(120 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests 100 concurrent read/ write operations using a single Sqlite Message Queue.")] + [TestMethod] + public void SqliteTestConcurrentReadWrite1() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid().ToString()}"; + const int numberMessages = 100; + + var expectedResults = new PushQueueItem[numberMessages]; + var actualResults = new PushQueueItem[numberMessages]; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + + for (var i = 0; i < numberMessages; i++) + { + expectedResults[i] = CreateRandomQueueItem(i); + } + + Parallel.For(0, numberMessages * 2, i => + { + if (i >= numberMessages) + { + var dequeueResult = TransactionalDequeue(messageQueue, timeoutMs: 60 * 1000); + + if (actualResults[dequeueResult.DequeueCount] != null) + { + throw new ArgumentException("This item has already been dequeued. Something is wrong."); + } + + actualResults[dequeueResult.DequeueCount] = dequeueResult; + } + else + { + Enqueue(expectedResults[i], messageQueue); + } + }); + } + + for (var i = 0; i < numberMessages; i++) + { + AssertCompare(expectedResults[i], actualResults[i]); + } + } + + [Timeout(120 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests 100 concurrent read/ write operations using multiple Sqlite Message Queues.")] + [TestMethod] + public void SqliteTestConcurrentReadWrite2() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid()}"; + const int numberMessages = 100; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + } + + var expectedResults = new PushQueueItem[numberMessages]; + var actualResults = new PushQueueItem[numberMessages]; + + for (var i = 0; i < numberMessages; i++) + { + expectedResults[i] = CreateRandomQueueItem(i); + } + + Parallel.For(0, numberMessages * 2, i => + { + if (i >= numberMessages) + { + var dequeueResult = Dequeue(messageQueuePath); + + if (actualResults[dequeueResult.DequeueCount] != null) + { + throw new ArgumentException("This item has already been dequeued. Something is wrong."); + } + + actualResults[dequeueResult.DequeueCount] = dequeueResult; + } + else + { + Enqueue(expectedResults[i], messageQueuePath); + } + }); + + for (var i = 0; i < numberMessages; i++) + { + AssertCompare(expectedResults[i], actualResults[i]); + } + } + + [Timeout(120 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests 40 concurrent read/ write across 4 threads reading/ writing 10 items each.")] + [TestMethod] + public void SqliteTestConcurrentReadWrite3() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid()}"; + + const int numberThreads = 4; + const int numberMessagesPerThread = 10; + const int numberMessages = numberThreads * numberMessagesPerThread; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + } + + var expectedResults = new PushQueueItem[numberMessages]; + var actualResults = new PushQueueItem[numberMessages]; + + for (var i = 0; i < numberMessages; i++) + { + expectedResults[i] = CreateRandomQueueItem(i); + } + + Parallel.For(0, numberThreads, i => + { + for (var ii = 0; ii < numberMessagesPerThread * 2; ii++) + { + // Switch between reading and writing + if (ii % 2 == 0) + { + Enqueue(expectedResults[(i * numberMessagesPerThread) + (ii / 2)], messageQueuePath); + } + else + { + var dequeueResult = Dequeue(messageQueuePath); + + if (actualResults[dequeueResult.DequeueCount] != null) + { + throw new ArgumentException("This item has already been dequeued. Something is wrong."); + } + + actualResults[dequeueResult.DequeueCount] = dequeueResult; + } + } + }); + + for (var i = 0; i < numberMessages; i++) + { + AssertCompare(expectedResults[i], actualResults[i]); + } + } + + [Timeout(240 * 1000)] + [TestCategory("SqliteMessageQueue")] + [Description("Tests 10 parallel read/ write operations in the same transaction.")] + [TestMethod] + public void SqliteTestConcurrentReadWrite4() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid()}"; + const int numberMessages = 10; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + messageQueue.Clear(); + } + + var expectedResults = new PushQueueItem[numberMessages]; + var actualResults = new PushQueueItem[numberMessages]; + + for (var i = 0; i < numberMessages; i++) + { + expectedResults[i] = CreateRandomQueueItem(i); + } + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + using (var messageQueueTransaction = messageQueue.CreateQueueTransaction()) + { + messageQueueTransaction.Begin(); + + Parallel.For(0, numberMessages * 2, i => + { + if (i % 2 == 0) + { + messageQueue.Enqueue(expectedResults[i / 2], messageQueueTransaction); + } + else + { + var dequeueResult = TryDequeue(messageQueue, messageQueueTransaction, timeoutMs: 120 * 1000); + + if (actualResults[dequeueResult.DequeueCount] != null) + { + throw new ArgumentException("This item has already been dequeued. Something is wrong."); + } + + actualResults[dequeueResult.DequeueCount] = dequeueResult; + } + }); + + messageQueueTransaction.Commit(); + } + } + + for (var i = 0; i < numberMessages; i++) + { + AssertCompare(expectedResults[i], actualResults[i]); + } + } + + [Timeout(30 * 1000)] + [Description("Attempts to dequeue from a SQLite queue that has not been created.")] + [TestCategory("SqliteMessageQueue")] + [TestMethod] + public void SqliteTestDequeueOnly1() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid()}"; + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + Assert.ThrowsException(() => messageQueue.DequeueNextMessage(queueTransaction)); + + queueTransaction.Abort(); + } + } + + [Timeout(30 * 1000)] + [Description("Tests we can enqueue and dequeue in the correct order from the Sqlite Message Queue")] + [TestCategory("SqliteMessageQueue")] + [TestMethod] + public void SqliteTestEnqueueDequeue1() + { + var messageQueuePath = $@".\Private$\{Guid.NewGuid()}"; + + var expectedItem1 = CreateRandomQueueItem(0); + var expectedItem2 = CreateRandomQueueItem(1); + + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + // Clear the queue + messageQueue.Clear(); + + queueTransaction.Begin(); + messageQueue.Enqueue(expectedItem1, queueTransaction); + messageQueue.Enqueue(expectedItem2, queueTransaction); + queueTransaction.Commit(); + } + + // Read from queue and then abort + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + var actualItem = messageQueue.DequeueNextMessage(queueTransaction); + AssertCompare(expectedItem1, actualItem); + + queueTransaction.Abort(); + } + + // Read from queue and the commit + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + var actualItem = messageQueue.DequeueNextMessage(queueTransaction); + AssertCompare(expectedItem1, actualItem); + + queueTransaction.Commit(); + } + + // Read from queue and check we can dequeue the second item. + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + var actualItem = messageQueue.DequeueNextMessage(queueTransaction); + AssertCompare(expectedItem2, actualItem); + + queueTransaction.Commit(); + } + + // Read from queue and check we can dequeue the second item. + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + + Assert.ThrowsException(() => messageQueue.DequeueNextMessage(queueTransaction)); + + queueTransaction.Abort(); + } + } + + private PushQueueItem CreateRandomQueueItem(int index = 0) + { + return new PushQueueItem( + new GatewayApplicationEntity(Guid.NewGuid().ToString(), index, Guid.NewGuid().ToString()), + Guid.NewGuid().ToString(), + Guid.NewGuid().ToString(), + Guid.NewGuid(), + DateTime.UtcNow, + Guid.NewGuid().ToString()) + { + DequeueCount = index + }; + } + + private void AssertCompare(PushQueueItem expected, PushQueueItem actual) + { + Assert.AreEqual(expected.AssociationGuid, actual.AssociationGuid); + Assert.AreEqual(expected.CalledApplicationEntityTitle, actual.CalledApplicationEntityTitle); + Assert.AreEqual(expected.CallingApplicationEntityTitle, actual.CallingApplicationEntityTitle); + Assert.AreEqual(expected.AssociationDateTime, actual.AssociationDateTime); + Assert.AreEqual(expected.DequeueCount, actual.DequeueCount); + Assert.AreEqual(expected.DestinationApplicationEntity.IpAddress, actual.DestinationApplicationEntity.IpAddress); + Assert.AreEqual(expected.DestinationApplicationEntity.Port, actual.DestinationApplicationEntity.Port); + Assert.AreEqual(expected.DestinationApplicationEntity.Title, actual.DestinationApplicationEntity.Title); + + for (var i = 0; i < expected.FilePaths.Count(); i++) + { + Assert.AreEqual(expected.FilePaths.ElementAt(i), actual.FilePaths.ElementAt(i)); + } + } + + private void Enqueue(PushQueueItem queueItem, string messageQueuePath) + { + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + Enqueue(queueItem, messageQueue); + } + } + + private void Enqueue(PushQueueItem queueItem, SqliteMessageQueue messageQueue) + { + using (var queueTransaction = messageQueue.CreateQueueTransaction()) + { + queueTransaction.Begin(); + messageQueue.Enqueue(queueItem, queueTransaction); + queueTransaction.Commit(); + } + } + + private PushQueueItem Dequeue(string messageQueuePath) + { + using (var messageQueue = new SqliteMessageQueue(messageQueuePath)) + { + return TransactionalDequeue(messageQueue, timeoutMs: 60 * 1000); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Microsoft.InnerEye.Listener.Tests.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Microsoft.InnerEye.Listener.Tests.csproj new file mode 100644 index 0000000..dbbab66 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Microsoft.InnerEye.Listener.Tests.csproj @@ -0,0 +1,61 @@ + + + net462 + x64 + Microsoft.InnerEye.Listener.Tests + 1.0.0.0 + Microsoft InnerEye (innereyedev@microsoft.com) + Microsoft Corporation + Microsoft InnerEye Gateway + Tests for Processor and Receiver for Microsoft InnerEye Gateway. + © Microsoft Corporation + https://github.com/microsoft/InnerEye-Gateway + https://github.com/microsoft/InnerEye-Gateway + win7-x64;win10-x64 + latest + AllEnabledByDefault + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Always + + + Always + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockAETConfigProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockAETConfigProvider.cs new file mode 100644 index 0000000..46bf135 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockAETConfigProvider.cs @@ -0,0 +1,24 @@ +namespace Microsoft.InnerEye.Listener.Tests.Models +{ + using System.Collections.Generic; + using Microsoft.InnerEye.Listener.Common; + + /// + /// Mock implementation of AETConfigModels provider. + /// + public class MockAETConfigProvider + { + private IEnumerable _aetConfigModels; + + public IEnumerable AETConfigModels() => _aetConfigModels; + + /// + /// Initializes a new instance of the class. + /// + /// Single AETConfigModel for the list. + public MockAETConfigProvider(AETConfigModel aetConfigModel) + { + _aetConfigModels = new[] { aetConfigModel }; + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockConfigurationProvider.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockConfigurationProvider.cs new file mode 100644 index 0000000..8a54e26 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockConfigurationProvider.cs @@ -0,0 +1,43 @@ +namespace Microsoft.InnerEye.Listener.Tests.Models +{ + using System; + using System.Collections.Generic; + + /// + /// Mock implementation of IConfigurationProvider. + /// + /// + public class MockConfigurationProvider + { + /// + /// Previous configuration, if there was a queue. + /// + private T _previousConfiguration; + + /// + /// TestException to throw on GetConfiguration to mock failure. + /// + public Exception TestException { get; set; } + + /// + /// Mock queue of configurations. + /// + public Queue ConfigurationQueue { get; } = new Queue(); + + /// + public T GetConfiguration() + { + if (TestException != null) + { + throw TestException; + } + + if (ConfigurationQueue.Count > 0) + { + _previousConfiguration = ConfigurationQueue.Dequeue(); + } + + return _previousConfiguration; + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockInnerEyeSegmentationClient.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockInnerEyeSegmentationClient.cs new file mode 100644 index 0000000..b80857b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/Models/MockInnerEyeSegmentationClient.cs @@ -0,0 +1,149 @@ +namespace Microsoft.InnerEye.Listener.Tests.Models +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Azure.Segmentation.Client; + + public class MockInnerEyeSegmentationClient : IInnerEyeSegmentationClient + { + /// + /// Top lvl replacements for deanonymizer + /// + private readonly IEnumerable _deAnonymizeTryAddReplaceAtTopLevel = new[] + { + // Patient module + DicomTag.PatientID, + DicomTag.PatientName, + DicomTag.PatientBirthDate, + DicomTag.PatientSex, + + // Study module + DicomTag.StudyDate, + DicomTag.StudyTime, + DicomTag.ReferringPhysicianName, + DicomTag.StudyID, + DicomTag.AccessionNumber, + DicomTag.StudyDescription, + }; + + private readonly InnerEyeSegmentationClient _InnerEyeSegmentationClient; + private bool disposedValue; + + public MockInnerEyeSegmentationClient(InnerEyeSegmentationClient InnerEyeSegmentationClient) + { + _InnerEyeSegmentationClient = InnerEyeSegmentationClient; + } + + public string SegmentationResultFile { get; set; } = @"Images\LargeSeriesWithContour\rtstruct.dcm"; + + public TimeSpan ResponseDelay { get; set; } = TimeSpan.FromMilliseconds(100); + + public ModelResult SegmentationProgressResult { get; set; } + + public Exception PingException { get; set; } + + public Exception SegmentationResultException { get; set; } + + public bool RealSegmentation { get; set; } = true; + + public IEnumerable SegmentationAnonymisationProtocol => _InnerEyeSegmentationClient.SegmentationAnonymisationProtocol; + + public Guid SegmentationAnonymisationProtocolId => _InnerEyeSegmentationClient.SegmentationAnonymisationProtocolId; + + public Task PingAsync() + { + return DelayAndThrowExceptionIfNotNull(PingException); + } + + public async Task SegmentationResultAsync(string modelId, string segmentationId, IEnumerable referenceDicomFiles, IEnumerable userSettingsForResultRTFile) + { + await DelayAndThrowExceptionIfNotNull(SegmentationResultException); + + if (RealSegmentation) + { + return await _InnerEyeSegmentationClient.SegmentationResultAsync(modelId, segmentationId, referenceDicomFiles, userSettingsForResultRTFile); + } + else if (SegmentationProgressResult != null) + { + return SegmentationProgressResult; + } + else + { + var dicomFile = await DicomFile.OpenAsync(SegmentationResultFile, FileReadOption.ReadAll); + + dicomFile.Dataset.AddOrUpdate(DicomTag.SoftwareVersions, + $@"InnerEye AI Model: Test.Name\" + + $@"InnerEye AI Model ID: Test.ID\" + + $@"InnerEye Model Created: Test.CreatedDate\" + + $@"InnerEye Version: Test.AssemblyVersion\"); + + dicomFile.Dataset.AddOrUpdate(DicomTag.SeriesDate, + $"{DateTime.UtcNow.Year}{DateTime.UtcNow.Month.ToString("D2")}{DateTime.UtcNow.Day.ToString("D2")}"); + + var anonymized = _InnerEyeSegmentationClient.DeAnonymize( + dicomFile, + referenceDicomFiles, + _deAnonymizeTryAddReplaceAtTopLevel, + userSettingsForResultRTFile, + SegmentationAnonymisationProtocolId, + SegmentationAnonymisationProtocol); + return new ModelResult(100, string.Empty, anonymized); + } + } + + public async Task<(string segmentationId, IEnumerable postedImages)> StartSegmentationAsync(string modelId, IEnumerable dicomFiles) + { + if (RealSegmentation) + { + return await _InnerEyeSegmentationClient.StartSegmentationAsync(modelId, dicomFiles); + } + else + { + return (Guid.NewGuid().ToString(), dicomFiles.SelectMany(x => x.DicomFiles).ToList()); + } + } + + public DicomFile AnonymizeDicomFile(DicomFile dicomFile, Guid anonymisationProtocolId, IEnumerable anonymisationProtocol) + { + return _InnerEyeSegmentationClient.AnonymizeDicomFile(dicomFile, anonymisationProtocolId, anonymisationProtocol); + } + + private async Task DelayAndThrowExceptionIfNotNull(Exception e) + { + await Task.Delay(ResponseDelay); + + if (e != null) + { + throw e; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposedValue) + { + return; + } + + if (disposing) + { + _InnerEyeSegmentationClient?.Dispose(); + } + + disposedValue = true; + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ConfigurationServiceTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ConfigurationServiceTests.cs new file mode 100644 index 0000000..5f076a8 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ConfigurationServiceTests.cs @@ -0,0 +1,110 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.Security.Authentication; + using System.Threading; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ConfigurationServiceTests : BaseTestClass + { + [TestCategory("ConfigurationService")] + [Timeout(100 * 1000)] + [Description("Starts the configuration service and throws an exception on start. Tests it stops correctly.")] + [TestMethod] + public void TestBadStart() + { + using (var configurationService = CreateConfigurationService( + null, + () => + { + throw new NotImplementedException(); + }, + CreateDownloadService(), + CreateUploadService())) + { + var cancelRequested = false; + + configurationService.StopRequested += (s, e) => + { + configurationService.OnStop(); + cancelRequested = true; + }; + + configurationService.Start(); + + SpinWait.SpinUntil(() => cancelRequested, TimeSpan.FromSeconds(10)); + + Assert.IsFalse(configurationService.IsExecutionThreadRunning); + } + } + + [TestCategory("ConfigurationService")] + [Timeout(100 * 1000)] + [Description("Starts the configuration service and throws an authentication exception on start. Tests it stops correctly.")] + [TestMethod] + public void TestBadProductKeyStart() + { + var segmentationClient = TestGatewayProcessorConfigProvider.CreateInnerEyeSegmentationClient(licenseKeyEnvVar: "NOT_AN_ENV_VAR").Invoke(); + using (var configurationService = CreateConfigurationService( + segmentationClient, + null, + CreateDownloadService(), + CreateUploadService())) + { + configurationService.Start(); + + SpinWait.SpinUntil(() => !configurationService.IsExecutionThreadRunning, TimeSpan.FromSeconds(10)); + + Assert.IsFalse(configurationService.IsExecutionThreadRunning); + } + } + + [TestCategory("ConfigurationService")] + [Timeout(100 * 1000)] + [Description("Starts the configuration service and then makes the segmentation client throw an authentication failure after start. Checks all services stop.")] + [TestMethod] + public void TestBadProductKeyAfterStart() + { + var client = GetMockInnerEyeSegmentationClient(); + // Create the services with multiple instances. + using (var downloadService = CreateDownloadService(null, 1, instances: 3)) + using (var uploadService = CreateUploadService(instances: 3)) + using (var configurationService = CreateConfigurationService( + client, + null, + downloadService, + uploadService)) + { + var cancelRequested = false; + + configurationService.StopRequested += (s, e) => + { + configurationService.OnStop(); + cancelRequested = true; + }; + + configurationService.Start(); + + SpinWait.SpinUntil(() => configurationService.IsExecutionThreadRunning, TimeSpan.FromSeconds(10)); + SpinWait.SpinUntil(() => downloadService.IsExecutionThreadRunning, TimeSpan.FromSeconds(10)); + SpinWait.SpinUntil(() => uploadService.IsExecutionThreadRunning, TimeSpan.FromSeconds(10)); + + // Check all services have started. + Assert.IsTrue(configurationService.IsExecutionThreadRunning); + Assert.IsTrue(downloadService.IsExecutionThreadRunning); + Assert.IsTrue(uploadService.IsExecutionThreadRunning); + + client.PingException = new AuthenticationException(); + + SpinWait.SpinUntil(() => cancelRequested, TimeSpan.FromSeconds(10)); + SpinWait.SpinUntil(() => !downloadService.IsExecutionThreadRunning, TimeSpan.FromSeconds(10)); + SpinWait.SpinUntil(() => !uploadService.IsExecutionThreadRunning, TimeSpan.FromSeconds(10)); + + Assert.IsTrue(cancelRequested); + Assert.IsFalse(downloadService.IsExecutionThreadRunning); + Assert.IsFalse(uploadService.IsExecutionThreadRunning); + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DicomAnonymisationTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DicomAnonymisationTests.cs new file mode 100644 index 0000000..3454187 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DicomAnonymisationTests.cs @@ -0,0 +1,370 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.InnerEye.Listener.Tests.Common.Helpers; + using Microsoft.InnerEye.Listener.Tests.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class DicomAnonymisationTests : BaseTestClass + { + [TestCategory("DicomAnonymisationDCMTK")] + [Timeout(180 * 1000)] + [Description("Pushes an entire CT Image Series using the dry-run model de-anonymisation configuration. " + + "Checks the result structure set is correctly de-anonymised and contains the Gateway version number. " + + "This test also uses dciodvfy to verify the result Dicom file is a valid file.")] + [TestMethod] + public async Task GenerateAndTestDeAnonymisedStructureSetFile() + { + var image = @"Images\LargeSeriesWithContour"; + var tempFolder = CreateTemporaryDirectory(); + + var segmentationClient = GetMockInnerEyeSegmentationClient(); + segmentationClient.RealSegmentation = false; + + var configType = AETConfigType.ModelWithResultDryRun; + var dryRunFolder = DryRunFolders.GetFolder(configType); + + var testAETConfigModel = GetTestAETConfigModel(); + + var newTestAETConfigModel = testAETConfigModel.With( + aetConfig: new ClientAETConfig( + new AETConfig( + configType, + testAETConfigModel.AETConfig.Config.ModelsConfig), + testAETConfigModel.AETConfig.Destination, + false)); + + var aetConfigProvider = new MockAETConfigProvider(newTestAETConfigModel); + + var gatewayConfig = GetTestGatewayReceiveConfig(); + var gatewayReceiveConfig = gatewayConfig.With( + new DicomEndPoint(gatewayConfig.GatewayDicomEndPoint.Title, 160, gatewayConfig.GatewayDicomEndPoint.Ip), + tempFolder.FullName); // Save all data in the temp folder + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService(aetConfigProvider.AETConfigModels)) + using (var downloadService = CreateDownloadService(segmentationClient, OneHourSecs)) + using (var uploadService = CreateUploadService(segmentationClient, aetConfigProvider.AETConfigModels)) + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + { + deleteService.Start(); + pushService.Start(); + downloadService.Start(); + uploadService.Start(); + receiveService.Start(); + + DcmtkHelpers.SendFolderUsingDCMTK( + image, + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: newTestAETConfigModel.CallingAET, + calledAETitle: newTestAETConfigModel.CalledAET); + + SpinWait.SpinUntil(() => tempFolder.GetDirectories().FirstOrDefault(x => x.FullName.Contains(dryRunFolder)) != null); + + var dryRunFolderDirectory = tempFolder.GetDirectories().First(x => x.FullName.Contains(dryRunFolder)).GetDirectories().First(); + + // Wait until we have all image files. + SpinWait.SpinUntil(() => dryRunFolderDirectory.GetFiles().Length == 1); + + // Wait for all files to save. + await Task.Delay(1000); + + var originalSlice = DicomFile.Open(new DirectoryInfo(image).GetFiles().First().FullName); + + Assert.IsNotNull(originalSlice); + + foreach (var file in dryRunFolderDirectory.GetFiles()) + { + var dicomFile = DicomFile.Open(file.FullName, FileReadOption.ReadAll); + + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.StudyDate), dicomFile.Dataset.GetSingleValue(DicomTag.StudyDate)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.AccessionNumber), dicomFile.Dataset.GetSingleValue(DicomTag.AccessionNumber)); + Assert.AreEqual("RTSTRUCT", dicomFile.Dataset.GetSingleValue(DicomTag.Modality)); + Assert.AreEqual("Microsoft Corporation", dicomFile.Dataset.GetSingleValue(DicomTag.Manufacturer)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.ReferringPhysicianName), dicomFile.Dataset.GetSingleValue(DicomTag.ReferringPhysicianName)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty), dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.StudyDescription, string.Empty)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.PatientName), dicomFile.Dataset.GetSingleValue(DicomTag.PatientName)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.PatientID), dicomFile.Dataset.GetSingleValue(DicomTag.PatientID)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.PatientBirthDate), dicomFile.Dataset.GetSingleValue(DicomTag.PatientBirthDate)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.StudyInstanceUID), dicomFile.Dataset.GetSingleValue(DicomTag.StudyInstanceUID)); + Assert.AreEqual(originalSlice.Dataset.GetSingleValue(DicomTag.StudyID), dicomFile.Dataset.GetSingleValue(DicomTag.StudyID)); + + Assert.IsTrue(dicomFile.Dataset.GetString(DicomTag.SoftwareVersions).StartsWith("Microsoft InnerEye Gateway:")); + Assert.IsTrue(dicomFile.Dataset.GetValue(DicomTag.SoftwareVersions, 1).StartsWith("InnerEye AI Model:")); + Assert.IsTrue(dicomFile.Dataset.GetValue(DicomTag.SoftwareVersions, 2).StartsWith("InnerEye AI Model ID:")); + Assert.IsTrue(dicomFile.Dataset.GetValue(DicomTag.SoftwareVersions, 3).StartsWith("InnerEye Model Created:")); + Assert.IsTrue(dicomFile.Dataset.GetValue(DicomTag.SoftwareVersions, 4).StartsWith("InnerEye Version:")); + + Assert.AreEqual("1.2.840.10008.5.1.4.1.1.481.3", dicomFile.Dataset.GetSingleValue(DicomTag.SOPClassUID)); + Assert.AreEqual($"{DateTime.UtcNow.Year}{DateTime.UtcNow.Month.ToString("D2")}{DateTime.UtcNow.Day.ToString("D2")}", dicomFile.Dataset.GetSingleValue(DicomTag.SeriesDate)); + Assert.IsTrue(dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SeriesInstanceUID, string.Empty).StartsWith("1.2.826.0.1.3680043.2")); + Assert.AreEqual("511091532", dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SeriesNumber, string.Empty)); + Assert.AreEqual("NOT FOR CLINICAL USE", dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SeriesDescription, string.Empty)); + Assert.IsTrue(dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.SOPInstanceUID, string.Empty).StartsWith("1.2.826.0.1.3680043.2")); + Assert.AreEqual("ANONYM", dicomFile.Dataset.GetSingleValueOrDefault(DicomTag.OperatorsName, string.Empty)); + + VerifyDicomFile(file.FullName); + } + } + } + + [TestCategory("DicomAnonymisationDCMTK")] + [Timeout(60 * 1000)] + [Description("Pushes an entire CT Image Series using the dry-run model configuration. " + + "Checks every Dicom slice in the series is correctly anonymised and contains the Gateway version number. " + + "This test also generates a sample anonymised CT Slice.")] + [TestMethod] + public async Task GenerateAndTestAnonymisedDicomCTFile() + { + var tempFolder = CreateTemporaryDirectory(); + + { + var configType = AETConfigType.ModelDryRun; + var dryRunFolder = DryRunFolders.GetFolder(configType); + + var segmentationClient = GetMockInnerEyeSegmentationClient(); + + var testAETConfigModel = GetTestAETConfigModel(); + var newTestAETConfigModel = testAETConfigModel.With( + aetConfig: new ClientAETConfig( + new AETConfig( + configType, + null), + testAETConfigModel.AETConfig.Destination, + false)); + + var aetConfigProvider = new MockAETConfigProvider(newTestAETConfigModel); + + var gatewayConfig = GetTestGatewayReceiveConfig(); + var gatewayReceiveConfig = gatewayConfig.With( + new DicomEndPoint(gatewayConfig.GatewayDicomEndPoint.Title, 161, gatewayConfig.GatewayDicomEndPoint.Ip), + tempFolder.FullName); // Save all data in the temp folder + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService(aetConfigProvider.AETConfigModels)) + using (var downloadService = CreateDownloadService(segmentationClient, OneHourSecs)) + using (var uploadService = CreateUploadService(segmentationClient, aetConfigProvider.AETConfigModels)) + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + { + deleteService.Start(); + pushService.Start(); + downloadService.Start(); + uploadService.Start(); + receiveService.Start(); + + DcmtkHelpers.SendFolderUsingDCMTK( + @"Images\1ValidSmall", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: newTestAETConfigModel.CallingAET, + calledAETitle: newTestAETConfigModel.CalledAET); + + SpinWait.SpinUntil(() => tempFolder.GetDirectories().FirstOrDefault(x => x.FullName.Contains(dryRunFolder)) != null); + + var dryRunFolderDirectory = tempFolder.GetDirectories().First(x => x.FullName.Contains(dryRunFolder)).GetDirectories().First(); + + // Wait until we have all image files. + SpinWait.SpinUntil(() => dryRunFolderDirectory.GetFiles().Length == 20); + + // Wait for all files to save. + await Task.Delay(200); + + var savedSampleFile = false; + + foreach (var file in dryRunFolderDirectory.GetFiles()) + { + var dicomFile = DicomFile.Open(file.FullName); + + AssertDicomFileIsAnonymised(dicomFile); + + if (!savedSampleFile) + { + WriteDicomFileForBuildPackage("AnonymisedCT.dcm", dicomFile); + savedSampleFile = true; + } + } + } + } + } + + [TestCategory("DicomAnonymisationDCMTK")] + [Timeout(60 * 1000)] + [Description("Pushes an entire RT structure set file and an CT Series using the dry-run feedback configuration. " + + "Checks the anonymised RT structure set file is correctly anonymised and contains the Gateway version number. " + + "This test also generates a sample Anonymised RT Structure Set.")] + [TestMethod] + public async Task GenerateAndTestAnonymisedRTFile() + { + var tempFolder = CreateTemporaryDirectory(); + + { + var configType = AETConfigType.ModelDryRun; + var dryRunFolder = DryRunFolders.GetFolder(configType); + + var segmentationClient = GetMockInnerEyeSegmentationClient(); + + var testAETConfigModel = GetTestAETConfigModel(); + var newTestAETConfigModel = testAETConfigModel.With( + aetConfig: new ClientAETConfig( + new AETConfig( + configType, + null), + testAETConfigModel.AETConfig.Destination, + false)); + + var aetConfigProvider = new MockAETConfigProvider(newTestAETConfigModel); + + var gatewayConfig = GetTestGatewayReceiveConfig(); + var gatewayReceiveConfig = gatewayConfig.With( + new DicomEndPoint(gatewayConfig.GatewayDicomEndPoint.Title, 162, gatewayConfig.GatewayDicomEndPoint.Ip), + tempFolder.FullName); // Save all data in the temp folder + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService(aetConfigProvider.AETConfigModels)) + using (var downloadService = CreateDownloadService(segmentationClient, OneHourSecs)) + using (var uploadService = CreateUploadService(segmentationClient, aetConfigProvider.AETConfigModels)) + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + { + deleteService.Start(); + pushService.Start(); + downloadService.Start(); + uploadService.Start(); + receiveService.Start(); + + DcmtkHelpers.SendFolderUsingDCMTK( + @"Images\1ValidSmall", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitRTCT, + TestContext, + applicationEntityTitle: newTestAETConfigModel.CallingAET, + calledAETitle: newTestAETConfigModel.CalledAET); + + SpinWait.SpinUntil(() => tempFolder.GetDirectories().FirstOrDefault(x => x.FullName.Contains(dryRunFolder)) != null); + + var dryRunFolderDirectory = tempFolder.GetDirectories().First(x => x.FullName.Contains(dryRunFolder)).GetDirectories().First(); + + // Wait until we have all image files. + SpinWait.SpinUntil(() => dryRunFolderDirectory.GetFiles().Length == 1); + + // Wait for all files to save. + await Task.Delay(1000); + + var savedSampleFile = false; + + foreach (var file in dryRunFolderDirectory.GetFiles()) + { + var dicomFile = DicomFile.Open(file.FullName); + + AssertDicomFileIsAnonymised(dicomFile); + + if (!savedSampleFile) + { + WriteDicomFileForBuildPackage("AnonymisedRT.dcm", dicomFile); + savedSampleFile = true; + } + } + } + } + } + + private void VerifyDicomFile(string path) + { + var verifierPath = Path.Combine("Assets", "dicom3tools", "dciodvfy.exe"); + Assert.IsNotNull(verifierPath, "DICOM verifier executable (dciodvfy.exe) not found on system PATH"); + + var process = new Process(); + process.StartInfo.FileName = verifierPath; + process.StartInfo.Arguments = path; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardError = true; + + process.Start(); + var standardError = process.StandardError.ReadToEnd(); + process.WaitForExit(); + + // Ignore empty contours + var output = standardError + .Replace("Error - Bad Sequence number of Items 0 (1-n Required by Module definition) Element= Module=", string.Empty) + .Replace("Error - Bad attribute Value Multiplicity Type 3 Optional Element= Module=", string.Empty) + .ToLower(); + + Assert.IsFalse(output.Contains("error")); + } + + private void AssertDicomFileIsAnonymised(DicomFile dicomFile) + { + // Check the software version gets added + var softwareVersion = dicomFile.Dataset.GetString(DicomTag.SoftwareVersions); + + Assert.IsTrue(softwareVersion.StartsWith("Microsoft InnerEye Gateway:")); + + var acceptedTags = new List() + { + DicomTag.ImageType, + DicomTag.SOPClassUID, + DicomTag.SOPInstanceUID, + DicomTag.Modality, + DicomTag.PatientID, + DicomTag.BodyPartExamined, + DicomTag.SoftwareVersions, + DicomTag.PatientPosition, + DicomTag.StudyInstanceUID, + DicomTag.SeriesInstanceUID, + DicomTag.ImagePositionPatient, + DicomTag.ImageOrientationPatient, + DicomTag.FrameOfReferenceUID, + DicomTag.SliceLocation, + DicomTag.SamplesPerPixel, + DicomTag.PhotometricInterpretation, + DicomTag.Rows, + DicomTag.Columns, + DicomTag.PixelSpacing, + DicomTag.BitsAllocated, + DicomTag.BitsStored, + DicomTag.HighBit, + DicomTag.PixelRepresentation, + DicomTag.RescaleIntercept, + DicomTag.RescaleSlope, + DicomTag.PixelData, + + // Structure set tags + DicomTag.StructureSetLabel, + DicomTag.StructureSetName, + DicomTag.ReferencedFrameOfReferenceSequence, + DicomTag.StructureSetROISequence, + DicomTag.ROIContourSequence, + DicomTag.RTROIObservationsSequence, + + // Deidentification + DicomTag.DeidentificationMethod, + DicomTag.PatientIdentityRemoved, + DicomTag.LongitudinalTemporalInformationModified, + }; + + var tagsInDataset = dicomFile.Dataset.Select(x => x.Tag).ToList(); + + Assert.IsTrue(tagsInDataset.Count <= 29); + + foreach (var item in tagsInDataset) + { + // Check that this is an accepted tag + Assert.IsTrue(acceptedTags.Contains(item), $"The Dicom file contained the Tag: {item.DictionaryEntry.Name}"); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DownloadServiceTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DownloadServiceTests.cs new file mode 100644 index 0000000..3ffab09 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/DownloadServiceTests.cs @@ -0,0 +1,398 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + using Newtonsoft.Json; + + [TestClass] + public class DownloadServiceTests : BaseTestClass + { + private readonly TagReplacement[] _defaultTagReplacement = new[] + { + new TagReplacement(TagReplacementOperation.UpdateIfExists, new DicomConstraints.DicomTagIndex(Dicom.DicomTag.Manufacturer), "MicrosoftCorp") + }; + + [TestCategory("DownloadService")] + [Ignore("Integration test, relies on live API")] + [Description("Test the happy end to end path of the download service using the live segmentation service.")] + [Timeout(240 * 1000)] + [TestMethod] + public async Task DownloadServiceLiveCloudMockConfigEndToEndTest() + { + var resultDirectory = CreateTemporaryDirectory(); + + var (segmentationId, modelId, data) = await StartRealSegmentationAsync(@"Images\1ValidSmall\"); + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 140, "localhost"); + + // Create a Data receiver to receive the RT struct result + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(CreateTemporaryDirectory().FullName))) + { + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1)); + + Assert.IsTrue(dicomDataReceiver.IsListening); + + using (var pushService = CreatePushService()) + using (var downloadService = CreateDownloadService(null, QuarterHourSecs)) + using (var downloadQueue = downloadService.DownloadQueue) + { + pushService.Start(); + downloadService.Start(); + + TransactionalEnqueue( + downloadQueue, + new DownloadQueueItem( + segmentationId: segmentationId, + modelId: modelId, + resultsDirectory: resultDirectory.FullName, + referenceDicomFiles: data, + calledApplicationEntityTitle: applicationEntity.Title, + callingApplicationEntityTitle: applicationEntity.Title, + destinationApplicationEntity: applicationEntity, + tagReplacementJsonString: JsonConvert.SerializeObject(_defaultTagReplacement), + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromMinutes(3)); + } + + dicomDataReceiver.StopServer(); + + Assert.IsFalse(string.IsNullOrWhiteSpace(folderPath)); + + var files = new DirectoryInfo(folderPath).GetFiles(); + + // Check we have a file + Assert.AreEqual(1, files.Length); + + var dicomFile = await DicomFile.OpenAsync(files[0].FullName); + + Assert.IsNotNull(dicomFile); + + TryDeleteDirectory(folderPath); + } + } + + [TestCategory("SystemTest")] + [Description("Creates the download service and makes sure the service dequeues the transactions on API errors.")] + [Timeout(60 * 1000)] + [TestMethod] + public void DownloadServiceAPIError() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var mockSegmentationClient = GetMockInnerEyeSegmentationClient(); + + // Set the client to always return 50% + mockSegmentationClient.SegmentationProgressResult = new ModelResult(100, "An API error.", null); + mockSegmentationClient.RealSegmentation = false; + + var referenceDicomFiles = new DirectoryInfo(@"Images\1ValidSmall\") + .GetFiles() + .Select(x => DicomFile.Open(x.FullName)) + .CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 141, "127.0.0.1"); + + using (var downloadService = CreateDownloadService(mockSegmentationClient, OneHourSecs)) + using (var downloadQueue = downloadService.DownloadQueue) + using (var deadLetterQueue = downloadService.DeadletterMessageQueue) + { + downloadService.Start(); + + Assert.IsTrue(downloadService.IsExecutionThreadRunning); + + TransactionalEnqueue( + downloadQueue, + new DownloadQueueItem( + segmentationId: Guid.NewGuid().ToString(), + modelId: Guid.NewGuid().ToString(), + resultsDirectory: CreateTemporaryDirectory().FullName, + referenceDicomFiles: referenceDicomFiles, + calledApplicationEntityTitle: applicationEntity.Title, + callingApplicationEntityTitle: applicationEntity.Title, + destinationApplicationEntity: applicationEntity, + tagReplacementJsonString: JsonConvert.SerializeObject(_defaultTagReplacement), + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + WaitUntilNoMessagesOnQueue(downloadQueue); + + // Check this message has been de-queued + Assert.ThrowsException(() => TransactionalDequeue(downloadQueue)); + Assert.ThrowsException(() => TransactionalDequeue(deadLetterQueue)); + } + } + + [TestCategory("SystemTest")] + [Description("Tests the download service cleans up images after failing to process a queue item.")] + [Timeout(60 * 1000)] + [TestMethod] + public void TestBadApiWhenAttemptingToSendImageWithResultsTest() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var dequeueServiceConfig = GetTestDequeueServiceConfig(maximumQueueMessageAgeSeconds: 1); + var mockSegmentationClient = GetMockInnerEyeSegmentationClient(); + + // Set the client to always return 50% + mockSegmentationClient.SegmentationResultException = new Exception(); + mockSegmentationClient.RealSegmentation = false; + + var resultsDirectory = CreateTemporaryDirectory(); + + // Copy files to result directory + new FileInfo(Directory.EnumerateFiles(@"Images\1ValidSmall\") + .ToList()[0]).CopyTo(Path.Combine(resultsDirectory.FullName, $"{Guid.NewGuid()}.dcm")); + + var referenceDicomFiles = resultsDirectory + .GetFiles() + .Select(x => DicomFile.Open(x.FullName)) + .CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 141, "127.0.0.1"); + + using (var deleteService = CreateDeleteService(dequeueServiceConfig)) + using (var deleteQueue = deleteService.DeleteQueue) + using (var downloadService = CreateDownloadService(mockSegmentationClient, 5, dequeueServiceConfig)) + using (var downloadQueue = downloadService.DownloadQueue) + { + deleteService.Start(); + downloadService.Start(); + + Assert.IsTrue(downloadService.IsExecutionThreadRunning); + + TransactionalEnqueue( + downloadQueue, + new DownloadQueueItem( + segmentationId: Guid.NewGuid().ToString(), + modelId: Guid.NewGuid().ToString(), + resultsDirectory: resultsDirectory.FullName, + referenceDicomFiles: referenceDicomFiles, + calledApplicationEntityTitle: applicationEntity.Title, + callingApplicationEntityTitle: applicationEntity.Title, + destinationApplicationEntity: applicationEntity, + tagReplacementJsonString: JsonConvert.SerializeObject(_defaultTagReplacement), + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + SpinWait.SpinUntil(() => new DirectoryInfo(resultsDirectory.FullName).Exists == false, TimeSpan.FromSeconds(60)); + + Assert.IsFalse(new DirectoryInfo(resultsDirectory.FullName).Exists); + } + } + + [TestCategory("SystemTest")] + [Description("Tests the download service stops when requested whilst stuck waiting for a download to complete.")] + [Timeout(60 * 1000)] + [TestMethod] + public void DownloadServiceExitsCorrectlyTest() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var mockSegmentationClient = GetMockInnerEyeSegmentationClient(); + + // Set the client to always return 50% + mockSegmentationClient.SegmentationProgressResult = new ModelResult(50, string.Empty, null); + mockSegmentationClient.RealSegmentation = false; + + var referenceDicomFiles = new DirectoryInfo(@"Images\1ValidSmall\") + .GetFiles() + .Select(x => DicomFile.Open(x.FullName)) + .CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 142, "127.0.0.1"); + + using (var downloadService = CreateDownloadService(mockSegmentationClient, OneHourSecs)) + using (var downloadQueue = downloadService.DownloadQueue) + { + TransactionalEnqueue( + downloadQueue, + new DownloadQueueItem( + segmentationId: Guid.NewGuid().ToString(), + modelId: Guid.NewGuid().ToString(), + resultsDirectory: CreateTemporaryDirectory().FullName, + referenceDicomFiles: referenceDicomFiles, + calledApplicationEntityTitle: applicationEntity.Title, + callingApplicationEntityTitle: applicationEntity.Title, + destinationApplicationEntity: applicationEntity, + tagReplacementJsonString: JsonConvert.SerializeObject(_defaultTagReplacement), + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + downloadService.Start(); + + SpinWait.SpinUntil(() => downloadService.IsExecutionThreadRunning); + + WaitUntilNoMessagesOnQueue(downloadQueue); + + Assert.IsTrue(downloadService.IsExecutionThreadRunning); + + downloadService.OnStop(); + + SpinWait.SpinUntil(() => !downloadService.IsExecutionThreadRunning); + + Assert.IsFalse(downloadService.IsExecutionThreadRunning); + } + } + + [TestCategory("DownloadService")] + [Description("Test the download service does not lose data when there is an error")] + [Timeout(240 * 1000)] + [TestMethod] + public void DownloadServiceInvalidDownloadId() + { + var segmentationAnonymisationProtocol = SegmentationAnonymisationProtocol(); + + var referenceDicomFiles = new DirectoryInfo(@"Images\1ValidSmall\") + .GetFiles() + .Select(x => DicomFile.Open(x.FullName)) + .CreateNewDicomFileWithoutPixelData(segmentationAnonymisationProtocol.Select(x => x.DicomTagIndex.DicomTag)); + + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 143, "127.0.0.1"); + + using (var downloadService = CreateDownloadService(null, 1, GetTestDequeueServiceConfig(100, 1000))) + using (var downloadQueue = downloadService.DownloadQueue) + using (var deadLetterQueue = downloadService.DeadletterMessageQueue) + { + downloadService.Start(); + + TransactionalEnqueue( + downloadQueue, + new DownloadQueueItem( + segmentationId: Guid.NewGuid().ToString(), + modelId: Guid.NewGuid().ToString(), + resultsDirectory: CreateTemporaryDirectory().FullName, + referenceDicomFiles: referenceDicomFiles, + calledApplicationEntityTitle: applicationEntity.Title, + callingApplicationEntityTitle: applicationEntity.Title, + destinationApplicationEntity: applicationEntity, + tagReplacementJsonString: JsonConvert.SerializeObject(_defaultTagReplacement), + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + var message = TransactionalDequeue(deadLetterQueue, timeoutMs: 60 * 1000); + + Assert.IsNotNull(message); + } + } + + [TestCategory("DownloadService")] + [Description("Test the download service recovers from the API going down.")] + [Timeout(240 * 1000)] + [TestMethod] + public async Task DownloadServiceNoApiConnection() + { + var (segmentationId, modelId, data) = await StartFakeSegmentationAsync(@"Images\1ValidSmall\"); + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 144, "127.0.0.1"); + + var mockSegmentationClient = GetMockInnerEyeSegmentationClient(); + mockSegmentationClient.RealSegmentation = false; + + // Fake a no response when getting progress + mockSegmentationClient.SegmentationResultException = new HttpRequestException(); + + // Create a Data receiver to receive the RT struct result + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(CreateTemporaryDirectory().FullName))) + { + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1)); + + Assert.IsTrue(dicomDataReceiver.IsListening); + + using (var pushService = CreatePushService()) + using (var downloadService = CreateDownloadService(mockSegmentationClient, QuarterHourSecs)) + using (var downloadQueue = downloadService.DownloadQueue) + using (var deadLetterQueue = downloadService.DeadletterMessageQueue) + { + pushService.Start(); + downloadService.Start(); + + downloadQueue.Clear(); + + TransactionalEnqueue( + downloadQueue, + new DownloadQueueItem( + segmentationId: segmentationId, + modelId: modelId, + resultsDirectory: CreateTemporaryDirectory().FullName, + referenceDicomFiles: data, + calledApplicationEntityTitle: applicationEntity.Title, + callingApplicationEntityTitle: applicationEntity.Title, + destinationApplicationEntity: applicationEntity, + tagReplacementJsonString: JsonConvert.SerializeObject(_defaultTagReplacement), + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + isDryRun: false)); + + // Wait + await Task.Delay(TimeSpan.FromSeconds(5)); + + // Null the exception from the mock client + mockSegmentationClient.SegmentationResultException = null; + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromMinutes(5)); + + WaitUntilNoMessagesOnQueue(downloadQueue); + + // Check this message has been de-queued + Assert.ThrowsException(() => TransactionalDequeue(downloadQueue)); + + Assert.ThrowsException(() => TransactionalDequeue(deadLetterQueue)); + } + + dicomDataReceiver.StopServer(); + + Assert.IsFalse(string.IsNullOrWhiteSpace(folderPath)); + + var files = new DirectoryInfo(folderPath).GetFiles(); + + // Check we have a file + Assert.AreEqual(1, files.Length); + + var dicomFile = await DicomFile.OpenAsync(files[0].FullName); + + Assert.IsNotNull(dicomFile); + + TryDeleteDirectory(folderPath); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/PushServiceTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/PushServiceTests.cs new file mode 100644 index 0000000..6cd4ec6 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/PushServiceTests.cs @@ -0,0 +1,196 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.DataProvider.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class PushServiceTests : BaseTestClass + { + [TestCategory("PushService")] + [Description("Tests a push queue item can be queued and dequeued..")] + [Timeout(120 * 1000)] + [TestMethod] + public void PushQueueItemTest() + { + using (var InnerEyeQueue = GetTestMessageQueue()) + { + var expected = + new PushQueueItem( + new GatewayApplicationEntity("Test1", 160, "127.0.0.1"), + "Test2", + "Test2", + Guid.NewGuid(), + DateTime.UtcNow, + "Test3", + "Test4", + "Test5"); + + TransactionalEnqueue(InnerEyeQueue, expected); + + var actual = TransactionalDequeue(InnerEyeQueue); + + Assert.AreEqual(expected.AssociationGuid, actual.AssociationGuid); + Assert.AreEqual(expected.CalledApplicationEntityTitle, actual.CalledApplicationEntityTitle); + Assert.AreEqual(expected.CallingApplicationEntityTitle, actual.CallingApplicationEntityTitle); + Assert.AreEqual(expected.AssociationDateTime, actual.AssociationDateTime); + Assert.AreEqual(expected.DestinationApplicationEntity.IpAddress, actual.DestinationApplicationEntity.IpAddress); + Assert.AreEqual(expected.DestinationApplicationEntity.Port, actual.DestinationApplicationEntity.Port); + Assert.AreEqual(expected.DestinationApplicationEntity.Title, actual.DestinationApplicationEntity.Title); + Assert.AreEqual(expected.FilePaths.ElementAt(0), actual.FilePaths.ElementAt(0)); + Assert.AreEqual(expected.FilePaths.ElementAt(1), actual.FilePaths.ElementAt(1)); + Assert.AreEqual(expected.FilePaths.ElementAt(2), actual.FilePaths.ElementAt(2)); + + } + } + + [TestCategory("PushService")] + [Description("Tests the push service can push results with images and structure sets.")] + [Timeout(120 * 1000)] + [TestMethod] + public async Task PushServiceTest() + { + var tempFolder = CreateTemporaryDirectory(); + + // Copy all files in the P4_Prostate directory to the temporary directory + Directory.EnumerateFiles(@"Images\1ValidSmall\") + .Select(x => new FileInfo(x)) + .ToList() + .ForEach(x => x.CopyTo(Path.Combine(tempFolder.FullName, x.Name))); + + var applicationEntity = new GatewayApplicationEntity("RListenerTest", 108, "127.0.0.1"); + var resultDirectory = CreateTemporaryDirectory(); + + // Create a Data receiver to receive the RT struct result + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultDirectory.FullName))) + { + var eventCount = 0; + + dicomDataReceiver.DataReceived += (sender, e) => + { + Interlocked.Increment(ref eventCount); + }; + + var started = dicomDataReceiver.StartServer(applicationEntity.Port, BuildAcceptedSopClassesAndTransferSyntaxes, TimeSpan.FromSeconds(1)); + + Assert.IsTrue(started); + Assert.IsTrue(dicomDataReceiver.IsListening); + + var dataSender = new DicomDataSender(); + var echoResult = await dataSender.DicomEchoAsync("RListener", applicationEntity.Title, applicationEntity.Port, applicationEntity.IpAddress); + + // Check echo + Assert.IsTrue(echoResult == DicomOperationResult.Success); + + var testAETConfigModel = GetTestAETConfigModel(); + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService()) + using (var pushQueue = pushService.PushQueue) + { + deleteService.Start(); + pushService.Start(); + + TransactionalEnqueue( + pushQueue, + new PushQueueItem( + destinationApplicationEntity: applicationEntity, + calledApplicationEntityTitle: testAETConfigModel.CalledAET, + callingApplicationEntityTitle: applicationEntity.Title, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + filePaths: tempFolder.GetFiles().Select(x => x.FullName).ToArray())); + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromMinutes(3)); + + SpinWait.SpinUntil(() => new DirectoryInfo(tempFolder.FullName).Exists == false, TimeSpan.FromSeconds(30)); + + Assert.IsFalse(new DirectoryInfo(tempFolder.FullName).Exists); + + Assert.AreEqual(20, resultDirectory.GetDirectories()[0].GetFiles().Length); + } + } + } + + [TestCategory("PushService")] + [Description("Checks the push service recovers from a bad destination.")] + [Timeout(120 * 1000)] + [TestMethod] + public async Task PushServiceBadAetTest() + { + var testAETConfigModel = GetTestAETConfigModel(); + var destination = testAETConfigModel.AETConfig.Destination; + + var tempFolder = CreateTemporaryDirectory(); + + // Grab a structure set file + var file = new FileInfo(@"Images\LargeSeriesWithContour\rtstruct.dcm"); + file.CopyTo(Path.Combine(tempFolder.FullName, file.Name)); + + var resultDirectory = CreateTemporaryDirectory(); + + // Create a Data receiver to receive the RT struct result + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultDirectory.FullName))) + { + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + dicomDataReceiver.StartServer( + destination.Port, + BuildAcceptedSopClassesAndTransferSyntaxes, + TimeSpan.FromSeconds(1)); + + var dataSender = new DicomDataSender(); + var echoResult = await dataSender.DicomEchoAsync( + "RListener", + destination.Title, + destination.Port, + destination.Ip); + + Assert.IsTrue(dicomDataReceiver.IsListening); + + // Check echo + Assert.IsTrue(echoResult == DicomOperationResult.Success); + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService()) + using (var pushQueue = pushService.PushQueue) + { + deleteService.Start(); + pushService.Start(); + + TransactionalEnqueue( + pushQueue, + new PushQueueItem( + destinationApplicationEntity: new GatewayApplicationEntity("", -1, "ababa"), + calledApplicationEntityTitle: testAETConfigModel.CalledAET, + callingApplicationEntityTitle: testAETConfigModel.CallingAET, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow, + filePaths: tempFolder.GetFiles().Select(x => x.FullName).ToArray())); + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromMinutes(3)); + + SpinWait.SpinUntil(() => new DirectoryInfo(tempFolder.FullName).Exists == false, TimeSpan.FromSeconds(30)); + + Assert.IsFalse(new DirectoryInfo(tempFolder.FullName).Exists); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ReceiveServiceTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ReceiveServiceTests.cs new file mode 100644 index 0000000..8250a3f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/ReceiveServiceTests.cs @@ -0,0 +1,275 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.Tests.Common.Helpers; + using Microsoft.InnerEye.Listener.Tests.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ReceiveServiceTests : BaseTestClass + { + [TestCategory("ReceiveServiceDCMTK")] + [Description("Checks the receive service restarts with a new configuration.")] + [Timeout(60000)] + [TestMethod] + public void ReceiveServiceRestartTest() + { + var callingAet = "ProstateRTMl"; + + var client = GetMockInnerEyeSegmentationClient(); + var resultDirectory = CreateTemporaryDirectory(); + var mockConfigurationServiceConfigProvider = new MockConfigurationProvider(); + + var gatewayConfig = GetTestGatewayReceiveConfig(); + + var configurationServiceConfig1 = new ConfigurationServiceConfig( + configurationRefreshDelaySeconds: 1); + + var configurationServiceConfig2 = new ConfigurationServiceConfig( + configurationServiceConfig1.ConfigCreationDateTime.AddSeconds(5), + configurationServiceConfig1.ApplyConfigDateTime.AddSeconds(10)); + + mockConfigurationServiceConfigProvider.ConfigurationQueue.Clear(); + mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig1); + mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig2); + + var mockReceiverConfigurationProvider2 = new MockConfigurationProvider(); + + var testReceiveServiceConfig = GetTestGatewayReceiveConfig(); + + var testReceiveServiceConfig1 = gatewayConfig.With( + new DicomEndPoint("TestAET1", 110, "127.0.0.1"), + resultDirectory.FullName); + + var testReceiveServiceConfig2 = gatewayConfig.With( + new DicomEndPoint("TestAET2", 111, "127.0.0.1"), + resultDirectory.FullName); + + mockReceiverConfigurationProvider2.ConfigurationQueue.Clear(); + mockReceiverConfigurationProvider2.ConfigurationQueue.Enqueue(testReceiveServiceConfig1); + mockReceiverConfigurationProvider2.ConfigurationQueue.Enqueue(testReceiveServiceConfig2); + + using (var receiveService = CreateReceiveService(mockReceiverConfigurationProvider2.GetConfiguration)) + using (var uploadQueue = receiveService.UploadQueue) + using (var configurationService = CreateConfigurationService( + client, + mockConfigurationServiceConfigProvider.GetConfiguration, + receiveService)) + { + // Start the service + configurationService.Start(); + + uploadQueue.Clear(); // Clear the message queue + + SpinWait.SpinUntil(() => receiveService.StartCount == 2); + + // Send on the new config + var result = DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + testReceiveServiceConfig1.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: callingAet, + calledAETitle: testReceiveServiceConfig1.GatewayDicomEndPoint.Title); + + // Check this did send on the old config + Assert.IsFalse(string.IsNullOrWhiteSpace(result)); + + // Send on the new config + result = DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + testReceiveServiceConfig2.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: callingAet, + calledAETitle: testReceiveServiceConfig2.GatewayDicomEndPoint.Title); + + // Check this did send on the new config + Assert.IsTrue(string.IsNullOrWhiteSpace(result)); + + var receiveQueueItem = TransactionalDequeue(uploadQueue); + + Assert.IsNotNull(receiveQueueItem); + Assert.AreEqual(callingAet, receiveQueueItem.CallingApplicationEntityTitle); + Assert.AreEqual(testReceiveServiceConfig2.GatewayDicomEndPoint.Title, receiveQueueItem.CalledApplicationEntityTitle); + + Assert.IsFalse(string.IsNullOrEmpty(receiveQueueItem.AssociationFolderPath)); + + var saveDirectoryInfo = new DirectoryInfo(receiveQueueItem.AssociationFolderPath); + + Assert.IsTrue(saveDirectoryInfo.Exists); + + var files = saveDirectoryInfo.GetFiles(); + + // Check we received one file over this association + Assert.AreEqual(1, files.Length); + + // Attempt to get another item from the queue + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue)); + } + } + + [TestCategory("ReceiveServiceDCMTK")] + [Description("Checks the receive service continues accepting data even when the API goes down.")] + [Timeout(60000)] + [TestMethod] + public void ReceiveServiceAPIDownTest() + { + var callingAet = "ProstateRTMl"; + + var mockReceiverConfigurationProvider = new MockConfigurationProvider(); + var client = GetMockInnerEyeSegmentationClient(); + + var gatewayReceiveConfig = GetTestGatewayReceiveConfig().With( + new DicomEndPoint("Gateway", 140, "localhost")); + + mockReceiverConfigurationProvider.ConfigurationQueue.Clear(); + mockReceiverConfigurationProvider.ConfigurationQueue.Enqueue(gatewayReceiveConfig); + + using (var receiveService = CreateReceiveService(mockReceiverConfigurationProvider.GetConfiguration)) + using (var uploadQueue = receiveService.UploadQueue) + { + receiveService.Start(); + + // This should cause an exception to be raised in ReceiveService.GetAcceptedSopClassesAndTransferSyntaxes + mockReceiverConfigurationProvider.TestException = new Exception("A general exception."); + + uploadQueue.Clear(); + + DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: callingAet, + calledAETitle: gatewayReceiveConfig.GatewayDicomEndPoint.Title); + + var receiveQueueItem = TransactionalDequeue(uploadQueue, 10000); + + Assert.IsNotNull(receiveQueueItem); + Assert.AreEqual(callingAet, receiveQueueItem.CallingApplicationEntityTitle); + Assert.AreEqual(gatewayReceiveConfig.GatewayDicomEndPoint.Title, receiveQueueItem.CalledApplicationEntityTitle); + + Assert.IsFalse(string.IsNullOrEmpty(receiveQueueItem.AssociationFolderPath)); + + var saveDirectoryInfo = new DirectoryInfo(receiveQueueItem.AssociationFolderPath); + + Assert.IsTrue(saveDirectoryInfo.Exists); + + var files = saveDirectoryInfo.GetFiles(); + + // Check we received one file over this association + Assert.AreEqual(1, files.Length); + + // Attempt to get another item from the queue + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue)); + } + } + + [TestCategory("ReceiveServiceDCMTK")] + [Description("Checks a valid end to end test of the receiver service by sending an item and picking it off the receive queue.")] + [Timeout(60000)] + [TestMethod] + public void ReceiveServiceLiveEndToEndTest() + { + var callingAet = "ProstateRTMl"; + + var config = TestGatewayReceiveConfigProvider.ReceiveServiceConfig(); + var gatewayReceiveConfig = config.With( + new DicomEndPoint("testname", 160, "localhost")); + + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + using (var uploadQueue = receiveService.UploadQueue) + { + uploadQueue.Clear(); // Clear the message queue + receiveService.Start(); // Start the service + + DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: callingAet, + calledAETitle: gatewayReceiveConfig.GatewayDicomEndPoint.Title); + + var receiveQueueItem = TransactionalDequeue(uploadQueue, timeoutMs: 20 * 1000); + + Assert.IsNotNull(receiveQueueItem); + Assert.AreEqual(callingAet, receiveQueueItem.CallingApplicationEntityTitle); + Assert.AreEqual(gatewayReceiveConfig.GatewayDicomEndPoint.Title, receiveQueueItem.CalledApplicationEntityTitle); + + Assert.IsFalse(string.IsNullOrEmpty(receiveQueueItem.AssociationFolderPath)); + + var saveDirectoryInfo = new DirectoryInfo(receiveQueueItem.AssociationFolderPath); + + Assert.IsTrue(saveDirectoryInfo.Exists); + + var files = saveDirectoryInfo.GetFiles(); + + // Check we received one file over this association + Assert.AreEqual(1, files.Length); + + // Attempt to get another item from the queue + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue)); + } + } + + [TestCategory("ReceiveServiceDCMTK")] + [Description("Checks the receive service does not enqueue a message on a Dicom echo.")] + [Timeout(60000)] + [TestMethod] + public async Task ReceiveServiceEchoTest() + { + var config = TestGatewayReceiveConfigProvider.ReceiveServiceConfig(); + var gatewayReceiveConfig = config.With( + new DicomEndPoint("testname", 180, "localhost")); + + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + using (var uploadQueue = receiveService.UploadQueue) + { + uploadQueue.Clear(); + receiveService.Start(); + + var sender = new DicomDataSender(); + + await sender.DicomEchoAsync( + "Hello", + gatewayReceiveConfig.GatewayDicomEndPoint.Title, + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + gatewayReceiveConfig.GatewayDicomEndPoint.Ip); + + // Check nothing is added to the message queue + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue, timeoutMs: 1000)); + + // Now check when we send a file a message is added + DcmtkHelpers.SendFileUsingDCMTK( + @"Images\1ValidSmall\1.dcm", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: "ProstateRTMl", + calledAETitle: gatewayReceiveConfig.GatewayDicomEndPoint.Title); + + Assert.IsNotNull(TransactionalDequeue(uploadQueue, timeoutMs: 1000)); + + // Now try another Dicom echo + await sender.DicomEchoAsync( + "Hello", + gatewayReceiveConfig.GatewayDicomEndPoint.Title, + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + gatewayReceiveConfig.GatewayDicomEndPoint.Ip); + + // Check nothing is added to the message queue + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue, timeoutMs: 1000)); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/SystemTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/SystemTests.cs new file mode 100644 index 0000000..3bfe3f8 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/SystemTests.cs @@ -0,0 +1,251 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + + using Dicom; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.DataProvider.Models; + using Microsoft.InnerEye.Listener.Tests.Common.Helpers; + using Microsoft.InnerEye.Listener.Tests.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class SystemTests : BaseTestClass + { + [TestCategory("ProcessorService")] + [Description("Checks the processor service restarts and continues executing correctly after restart.")] + [Timeout(180 * 1000)] + [TestMethod] + public async Task ProcessorServiceRestartTest() + { + var tempFolder = CreateTemporaryDirectory(); + + foreach (var file in new DirectoryInfo(@"Images\1ValidSmall\").GetFiles()) + { + file.CopyTo(Path.Combine(tempFolder.FullName, file.Name)); + } + + var client = GetMockInnerEyeSegmentationClient(); + client.RealSegmentation = false; + + var mockConfigurationServiceConfigProvider = new MockConfigurationProvider(); + + var configurationServiceConfig1 = new ConfigurationServiceConfig( + configurationRefreshDelaySeconds: 1); + + var configurationServiceConfig2 = new ConfigurationServiceConfig( + configurationServiceConfig1.ConfigCreationDateTime.AddSeconds(5), + configurationServiceConfig1.ApplyConfigDateTime.AddSeconds(10)); + + mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig1); + mockConfigurationServiceConfigProvider.ConfigurationQueue.Enqueue(configurationServiceConfig2); + + var resultDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultDirectory.FullName))) + { + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + var testAETConfigModel = GetTestAETConfigModel(); + dicomDataReceiver.StartServer( + testAETConfigModel.AETConfig.Destination.Port, + BuildAcceptedSopClassesAndTransferSyntaxes, + TimeSpan.FromSeconds(1)); + + using (var pushService = CreatePushService()) + using (var uploadService = CreateUploadService(client)) + using (var uploadQueue = uploadService.UploadQueue) + using (var downloadService = CreateDownloadService(client, OneHourSecs)) + using (var configurationService = CreateConfigurationService( + client, + mockConfigurationServiceConfigProvider.GetConfiguration, + downloadService, + uploadService, + pushService)) + { + // Start the service + configurationService.Start(); + + uploadQueue.Clear(); // Clear the message queue + + SpinWait.SpinUntil(() => pushService.StartCount == 2); + SpinWait.SpinUntil(() => uploadService.StartCount == 2); + SpinWait.SpinUntil(() => downloadService.StartCount == 2); + + TransactionalEnqueue( + uploadQueue, + new UploadQueueItem( + calledApplicationEntityTitle: testAETConfigModel.CalledAET, + callingApplicationEntityTitle: testAETConfigModel.CallingAET, + associationFolderPath: tempFolder.FullName, + rootDicomFolderPath: tempFolder.FullName, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow)); + + SpinWait.SpinUntil(() => eventCount >= 3); + + Assert.IsFalse(string.IsNullOrWhiteSpace(folderPath)); + + var dicomFile = await DicomFile.OpenAsync(new DirectoryInfo(folderPath).GetFiles()[0].FullName); + + Assert.IsNotNull(dicomFile); + + dicomFile = null; + + TryDeleteDirectory(folderPath); + } + } + } + + [TestCategory("SystemTest")] + [Description("Creates a service with a large delay between each execution loop. Checks that when stop is called, the service stops within 1 second.")] + [Timeout(60 * 1000)] + [TestMethod] + public async Task ServiceBaseExitsCorrectlyTest() + { + using (var downloadService = CreateDownloadService(null, OneHourSecs)) + { + downloadService.Start(); + + await Task.Delay(1000); + + downloadService.OnStop(); + + Assert.IsFalse(downloadService.IsExecutionThreadRunning); + } + } + + [TestCategory("SystemTestDCMTK")] + [Description("Creates all services and pushes an entire CT image through the end to end system.")] + [Timeout(180 * 1000)] + [TestMethod] + public async Task GatewayLiveEndToEndTest() + { + var testAETConfigModel = GetTestAETConfigModel(); + + // Change this to real client to run a live pipeline + var segmentationClient = GetMockInnerEyeSegmentationClient(); + segmentationClient.RealSegmentation = false; + + var gatewayReceiveConfig = GetTestGatewayReceiveConfig().With( + new DicomEndPoint("Gateway", 141, "localhost")); + + var resultDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultDirectory.FullName))) + { + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + var result = dicomDataReceiver.StartServer( + testAETConfigModel.AETConfig.Destination.Port, + BuildAcceptedSopClassesAndTransferSyntaxes, + TimeSpan.FromSeconds(1)); + + Assert.IsTrue(result); + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService()) + using (var downloadService = CreateDownloadService(segmentationClient, OneHourSecs)) + using (var uploadService = CreateUploadService(segmentationClient)) + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + { + deleteService.Start(); + pushService.Start(); + downloadService.Start(); + uploadService.Start(); + receiveService.Start(); + + var dicomDataSender = new DicomDataSender(); + var echoResult = await dicomDataSender.DicomEchoAsync( + testAETConfigModel.CallingAET, + gatewayReceiveConfig.GatewayDicomEndPoint.Title, + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + gatewayReceiveConfig.GatewayDicomEndPoint.Ip); + + Assert.IsTrue(echoResult == DicomOperationResult.Success); + + DcmtkHelpers.SendFolderUsingDCMTK( + @"Images\1ValidSmall", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: testAETConfigModel.CallingAET, + calledAETitle: testAETConfigModel.CalledAET); + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromMinutes(3)); + + Assert.IsFalse(string.IsNullOrWhiteSpace(folderPath)); + + var dicomFile = await DicomFile.OpenAsync(new DirectoryInfo(folderPath).GetFiles()[0].FullName); + + Assert.IsNotNull(dicomFile); + + dicomFile = null; + + TryDeleteDirectory(folderPath); + } + } + } + + [TestCategory("SystemTestDCMTK")] + [Description("Creates all services and pushes an invalid file into the system.")] + [Timeout(180 * 1000)] + [TestMethod] + public async Task GatewayBadData() + { + var segmentationClient = GetMockInnerEyeSegmentationClient(); + var testAETConfigModel = GetTestAETConfigModel(); + var gatewayReceiveConfig = GetTestGatewayReceiveConfig(); + + using (var deleteService = CreateDeleteService()) + using (var pushService = CreatePushService()) + using (var downloadService = CreateDownloadService(segmentationClient, OneHourSecs)) + using (var uploadService = CreateUploadService(segmentationClient)) + using (var receiveService = CreateReceiveService(() => gatewayReceiveConfig)) + using (var uploadQueue = receiveService.UploadQueue) + { + deleteService.Start(); + pushService.Start(); + downloadService.Start(); + uploadService.Start(); + receiveService.Start(); + + DcmtkHelpers.SendFileUsingDCMTK( + @"Images\LargeSeriesWithContour\rtstruct.dcm", + gatewayReceiveConfig.GatewayDicomEndPoint.Port, + ScuProfile.LEExplicitCT, + TestContext, + applicationEntityTitle: testAETConfigModel.CallingAET, + calledAETitle: testAETConfigModel.CalledAET); + + await Task.Delay(1000); + + WaitUntilNoMessagesOnQueue(uploadQueue); + + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue)); + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/UploadServiceTests.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/UploadServiceTests.cs new file mode 100644 index 0000000..be8027b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/ServiceTests/UploadServiceTests.cs @@ -0,0 +1,246 @@ +namespace Microsoft.InnerEye.Listener.Tests.ServiceTests +{ + using System; + using System.IO; + using System.Threading; + + using Microsoft.InnerEye.Azure.Segmentation.API.Common; + using Microsoft.InnerEye.DicomConstraints; + using Microsoft.InnerEye.Gateway.MessageQueueing.Exceptions; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.DataProvider.Implementations; + using Microsoft.InnerEye.Listener.Tests.Models; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class UploadServiceTests : BaseTestClass + { + [TestCategory("UploadService")] + [Description("Tests to the upload service with a strange config (duplicate model in the same config).")] + [Timeout(60 * 1000)] + [TestMethod] + public void StrangeConfigUploadServiceTest() + { + var segmentationClient = GetMockInnerEyeSegmentationClient(); + segmentationClient.RealSegmentation = false; + + var testAETConfigModel = GetTestAETConfigModel(); + var newTestAETConfigModel = testAETConfigModel.With( + aetConfig: new ClientAETConfig( + new AETConfig( + AETConfigType.Model, + new[] + { + new ModelConstraintsConfig( + "b033d049-0233-4068-bc0f-c64cec48e8fa", + new [] { new ModelChannelConstraints("ct", new GroupConstraint(new DicomConstraint[0], LogicalOperator.And), new GroupConstraint(new DicomConstraint[0], LogicalOperator.And), -1, -1) }, + new TagReplacement[0]), + new ModelConstraintsConfig( + "b033d049-0233-4068-bc0f-c64cec48e8fa", + new [] { new ModelChannelConstraints("ct", new GroupConstraint(new DicomConstraint[0], LogicalOperator.And), new GroupConstraint(new DicomConstraint[0], LogicalOperator.And), -1, -1) }, + new TagReplacement[0]), + }), + testAETConfigModel.AETConfig.Destination, + false)); + + var aetConfigProvider = new MockAETConfigProvider(newTestAETConfigModel); + + using (var deleteService = CreateDeleteService()) + using (var uploadService = CreateUploadService(segmentationClient, aetConfigProvider.AETConfigModels)) + using (var uploadQueue = uploadService.UploadQueue) + using (var downloadQueue = uploadService.DownloadQueue) + { + deleteService.Start(); + uploadService.Start(); + + var tempFolder = CreateTemporaryDirectory(); + + foreach (var file in new DirectoryInfo(@"Images\1ValidSmall\").GetFiles()) + { + file.CopyTo(Path.Combine(tempFolder.FullName, file.Name)); + } + + Enqueue( + uploadQueue, + new UploadQueueItem( + calledApplicationEntityTitle: newTestAETConfigModel.CalledAET, + callingApplicationEntityTitle: newTestAETConfigModel.CallingAET, + associationFolderPath: tempFolder.FullName, + rootDicomFolderPath: tempFolder.FullName, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow), + true); + + // Leave enough time to allow upload + SpinWait.SpinUntil(() => new DirectoryInfo(tempFolder.FullName).Exists == false, TimeSpan.FromSeconds(300)); + + Assert.IsFalse(new DirectoryInfo(tempFolder.FullName).Exists); + + TransactionalDequeue(downloadQueue); + } + } + + [TestCategory("UploadService")] + [Description("Test the happy end to end path of the upload service using the live segmentation service.")] + [Timeout(180 * 1000)] + [TestMethod] + public void UploadServiceMockEndToEndTest() + { + var testAETConfigModel = GetTestAETConfigModel(); + + var tempFolder = CreateTemporaryDirectory(); + + foreach (var file in new DirectoryInfo(@"Images\LargeSeriesWithContour\").GetFiles()) + { + file.CopyTo(Path.Combine(tempFolder.FullName, file.Name)); + } + + var segmentationClient = GetMockInnerEyeSegmentationClient(); + segmentationClient.RealSegmentation = false; + + using (var deleteService = CreateDeleteService()) + using (var downloadService = CreateDownloadService(segmentationClient, OneMinSecs)) + using (var uploadService = CreateUploadService(segmentationClient)) + using (var downloadQueue = downloadService.DownloadQueue) + using (var uploadQueue = uploadService.UploadQueue) + { + downloadQueue.Clear(); + + deleteService.Start(); + downloadService.Start(); + uploadService.Start(); + + Enqueue( + uploadQueue, + new UploadQueueItem( + calledApplicationEntityTitle: testAETConfigModel.CalledAET, + callingApplicationEntityTitle: testAETConfigModel.CallingAET, + associationFolderPath: tempFolder.FullName, + rootDicomFolderPath: tempFolder.FullName, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow), + true); + + // Leave enough time to allow upload + SpinWait.SpinUntil(() => new DirectoryInfo(tempFolder.FullName).Exists == false); + + Assert.IsFalse(new DirectoryInfo(tempFolder.FullName).Exists); + + WaitUntilNoMessagesOnQueue(uploadQueue); + + // Make sure the item was dequeued + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue)); + + WaitUntilNoMessagesOnQueue(downloadQueue); + + Assert.ThrowsException(() => TransactionalDequeue(downloadQueue)); + + uploadService.OnStop(); + + // Allow a few seconds for the execution thread to exit. + SpinWait.SpinUntil(() => uploadService.IsExecutionThreadRunning == false); + + Assert.IsFalse(uploadService.IsExecutionThreadRunning); + } + } + + [TestCategory("UploadService")] + [Description("Test the happy end to end path of the upload service using the live segmentation service and pushing an image with the result.")] + [Timeout(180 * 1000)] + [TestMethod] + public void UploadServiceLiveSendImageTest() + { + var tempFolder = CreateTemporaryDirectory(); + + foreach (var file in new DirectoryInfo(@"Images\1ValidSmall\").GetFiles()) + { + file.CopyTo(Path.Combine(tempFolder.FullName, file.Name)); + } + + var segmentationClient = GetMockInnerEyeSegmentationClient(); + segmentationClient.RealSegmentation = false; + + var testAETConfigModel = GetTestAETConfigModel(); + var newTestAETConfigModel = testAETConfigModel.With( + aetConfig: new ClientAETConfig( + new AETConfig( + AETConfigType.Model, + new[] + { + new ModelConstraintsConfig( + "b033d049-0233-4068-bc0f-c64cec48e8fa", + new [] { new ModelChannelConstraints("ct", new GroupConstraint(new DicomConstraint[0], LogicalOperator.And), new GroupConstraint(new DicomConstraint[0], LogicalOperator.And), -1, -1) }, + new TagReplacement[0]), + }), + testAETConfigModel.AETConfig.Destination, + true)); + + var aetConfigProvider = new MockAETConfigProvider(newTestAETConfigModel); + + var resultDirectory = CreateTemporaryDirectory(); + + using (var dicomDataReceiver = new ListenerDataReceiver(new ListenerDicomSaver(resultDirectory.FullName))) + { + var eventCount = 0; + var folderPath = string.Empty; + + dicomDataReceiver.DataReceived += (sender, e) => + { + folderPath = e.FolderPath; + Interlocked.Increment(ref eventCount); + }; + + var result = dicomDataReceiver.StartServer( + newTestAETConfigModel.AETConfig.Destination.Port, + BuildAcceptedSopClassesAndTransferSyntaxes, + TimeSpan.FromSeconds(5)); + + Assert.IsTrue(result); + + using (var deleteService = CreateDeleteService()) + using (var uploadService = CreateUploadService(segmentationClient, aetConfigProvider.AETConfigModels)) + using (var uploadQueue = uploadService.UploadQueue) + using (var downloadService = CreateDownloadService(segmentationClient, OneMinSecs)) + using (var downloadQueue = downloadService.DownloadQueue) + using (var pushService = CreatePushService(aetConfigProvider.AETConfigModels)) + using (var pushQueue = pushService.PushQueue) + { + deleteService.Start(); + uploadService.Start(); + downloadService.Start(); + pushService.Start(); + + Enqueue( + uploadQueue, + new UploadQueueItem( + calledApplicationEntityTitle: newTestAETConfigModel.CalledAET, + callingApplicationEntityTitle: newTestAETConfigModel.CallingAET, + associationFolderPath: tempFolder.FullName, + rootDicomFolderPath: CreateTemporaryDirectory().FullName, + associationGuid: Guid.NewGuid(), + associationDateTime: DateTime.UtcNow), + true); + + // Leave enough time to allow upload + SpinWait.SpinUntil(() => new DirectoryInfo(tempFolder.FullName).Exists == false, TimeSpan.FromSeconds(90)); + + // Check the association folder is deleted. + Assert.IsFalse(new DirectoryInfo(tempFolder.FullName).Exists); + + // Wait for all events to finish on the data received + SpinWait.SpinUntil(() => eventCount >= 3, TimeSpan.FromMinutes(3)); + + // Make sure the item was dequeued + Assert.ThrowsException(() => TransactionalDequeue(uploadQueue)); + Assert.ThrowsException(() => TransactionalDequeue(downloadQueue)); + Assert.ThrowsException(() => TransactionalDequeue(pushQueue)); + + Assert.IsFalse(string.IsNullOrWhiteSpace(folderPath)); + + // Check we received the image in the result + Assert.AreEqual(21, new DirectoryInfo(folderPath).GetFiles().Length); + } + } + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfig.json b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfig.json new file mode 100644 index 0000000..c998380 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfig.json @@ -0,0 +1,102 @@ +[ + { + "CallingAET": "SCANNER", + "CalledAET": "RGPelvisCT", + "AETConfig": { + "Config": { + "AETConfigType": "Model", + "ModelsConfig": [ + { + "ModelId": "PassThroughModel:3", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": -1, + "MaxChannelImages": -1 + } + ], + "TagReplacements": [ + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 8, + "Element": 4158 + }, + "Value": "NOT FOR CLINICAL USE" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 2 + }, + "Value": "InnerEye" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 4 + }, + "Value": "NOT FOR CLINICAL USE" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 8, + "Element": 112 + }, + "Value": "Microsoft Corporation" + }, + { + "Operation": "AppendIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 38 + }, + "Value": " NOT FOR CLINICAL USE" + } + ] + }, + { + "ModelId": "PassThroughModel:3", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": -1, + "MaxChannelImages": -1 + } + ], + "TagReplacements": [] + } + ] + }, + "Destination": { + "Title": "PACS", + "Port": 105, + "Ip": "localhost" + }, + "ShouldReturnImage": false + } + } +] \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayProcessorConfig.json b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayProcessorConfig.json new file mode 100644 index 0000000..0cd9f47 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayProcessorConfig.json @@ -0,0 +1,22 @@ +{ + "ServiceSettings": { + "RunAsConsole": true + }, + "ProcessorSettings": { + "LicenseKeyEnvVar": "INNEREYE_INFERENCE_DEV_KEY", + "InferenceUri": "https://innereyeinferencedev.azurewebsites.net" + }, + "DequeueServiceConfig": { + "MaximumQueueMessageAgeSeconds": 100, + "DeadLetterMoveFrequencySeconds": 1801 + }, + "DownloadServiceConfig": { + "DownloadRetryTimespanInSeconds": 5, + "DownloadWaitTimeoutInSeconds": 3600 + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2020-05-31T20:14:51", + "ApplyConfigDateTime": "2020-05-31T20:14:51", + "ConfigurationRefreshDelaySeconds": "1" + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayReceiveConfig.json b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayReceiveConfig.json new file mode 100644 index 0000000..0d6dcba --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Tests/TestConfigurations/GatewayReceiveConfig.json @@ -0,0 +1,24 @@ +{ + "ServiceSettings": { + "RunAsConsole": true + }, + "ReceiveServiceConfig": { + "GatewayDicomEndPoint": { + "Title": "GATEWAY", + "Port": 104, + "Ip": "localhost" + }, + "RootDicomFolder": "C:\\InnerEyeGateway\\", + "AcceptedSopClassesAndTransferSyntaxesUIDs": { + "1.2.840.10008.1.1": [ "1.2.840.10008.1.2.1", "1.2.840.10008.1.2" ], + "1.2.840.10008.5.1.4.1.1.481.3": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1" ], + "1.2.840.10008.5.1.4.1.1.2": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.5" ], + "1.2.840.10008.5.1.4.1.1.4": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.5" ] + } + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2018-07-25T20:14:51.539351Z", + "ApplyConfigDateTime": "2018-07-25T20:14:51.539351Z", + "ConfigurationRefreshDelaySeconds": 60 + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Assets/Icon.ico b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Assets/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..45fdc3ba5ef6e50af80b2a960696214910d8b737 GIT binary patch literal 367863 zcmeHw34k0$)ql-y$c7Nu6+{s7Wd{w4;aUW_4B6pOKm=DL9C8gPa)`LfeT40RBK#2- zfe!(}i4qXPv4V;mff+spj{u8^c%d_ZatN}^Aqip0{C}^f-lV3sd#3N{IR+}P>Q&Wy zzxS(G)jhqlvqB`q`l6>tKqkd;Q-nA$A;f#%TWa4uRfq@H5Msj(OYIBS6ymfQLd=|5 zYX2GaRqG2eYgVcKlXZl6>H|Xb_LkZ=bqjI9HbNwmvJF35?JC4(o0ZzfZG`ck6=Keu zQhVC}2r=(OA-3MS)Sf+Ch)-T5L|>nrzo`%>+$O}1JC@onepHBc{~*NNxuy1AI}5RL zjfCj$m-ACX?3PN16ePvB*=|C7$w-J#ezMdS144Y~{}SSW14`|KzmE309U`4BwQssY zi08l2Aq+##pOz5cd4H$4^2$gtmcv}IiJT*lCqyLPwI0rA7eOPPxGaJ z=4z6S8u(2P?XcItKZ$?x<>lqkzZom}!+fvtkIDHNI}Eaa60dT6M)rGM|0K<)7wk>S zUGgFOto#p}6P16gY}m}7G?&f52Ir&6sWPA1#8Z;TvMd5+;d~(;ObD?M1^pZ3Y%yXCabZWA3835>Lqbbik-%yPq#tt4p6bZgsoVXQcWtz7O*TF&}nzA2+X8cuOG&c)hP% z6rcVA7fBSluNXyP&Jj@Q1zidytMt(rYNvRNl|+9J$}kGsEK44bK3b=;-lCr7v8u#4 z%5N2Kg$){ue!^X$w`r#=Y>(NDHSJ0JBh|(zeOa~-9dTldo99UTPOz8p$T`Doqnh+$ zANG>fY^r@8?NbgqirJ-olYiK*$vgXZX+Pz1ywm#$;v!A9>BZQbNC)F7cT+Sjs+SYo zJcclF-HD87d|3H2D)EPv|AW$gSo=3g_UVPS3i|vRE?F-93;RKypR}Ik>m2)mt`B7l z#r7Pva+X!ZxiW##3GmqLky& z?314QlUDOtSHuH=i5XpDe8Wyr=t+niiXn@DTwU-`pr;G>UpPi9^rbOIPbcPeVtyCw zbc^wxUNO-#WZsA2QLez_)JX5c5VtEnnGcqjPq{+tQs!5;iMD}~M^ST@+JkJTmc-Z; zN>;OB+T*$CqnuK4sQ$Q4{kAbn8x#+ZpClE3*=pmIKJyIac`T)`T6p<-oL1~j!er4` z!L3tYWQXuU*2Jax%yV3FepL?DxpKUdm@3AwKiHz&sOYQK_UaEdlX7mhnti>`t3Nr< zRcz$jqd#-~FyHaIahx9g8FHT6Jo6G?dH*qHH_~N%lpE#q#f={x>(C$ZmZq`wd@*J> zlJL42&tvN}FN*x6vy1PuquPJwy(|3E_1p2;Nn<^(LyG3n3pT<&KeHuIrGMei)fp-= zy5S_H`jhiBPI-0d&s;YPe@^aw%$QvGquei_WnO(c^jFmv`zAanRriPke>C4VF7~BL zjHev3%-bsdXnabw!#8lBYj#8K+dUCQD`1k}$ zk62y$P^U7E_cP;)zC6Z2Kb71RF-WC=vEx$`;}cW5#*0&W$LZ&y_(+Pt$3h}~U*m)e zr(?kF07?d>yauROA@TYo&B=5xJ@r2S3n z=;)fwn8K7*uvX1+hw)n4sPo(?^O@owmGRQLTk=s&?}Mw1oE!CdH9&spg}Cz7VzcU# z^W5^3Hyz4M07w`|WW^rcW7*>V|gN{>@- zOW%aAs(*)m$xcn5i~(bb^Q!CZz%R|W<(7R}Nz9`>&!bF=y^3ENpSH5+RtGzDwq#Js z9B6(?PyOXHp_UK4-<&8e%*|cZY0O;FDQ1j!h$#h>oGeC%5GYf`>M{fMnJ9B5(cu|r4W zhEc|-Un$fk-jezpyEev>EdxcjHOV)|(7IWUgPw6vf7;F;>z&wA#yUPHBo15u=qG*{ ztF0v87)RXHidVNm=y3 zZe9<)`t|6W;!P1|dPU@W6n+L#dP-jF|8GQbd&;M3*!->;Y}X^ti3jpqkNr))$#1>R zHS$}J^PKj%x!)iS6ym{E@sVo=?{LC^LRg~kU5;7tKM;{`%l9_$opIye1Lqlz)k?bY z@6vZwzl_DHe;2+T`BnVW`d7_A2fk^(<@&N8JBfLe|Fr)sF;wwQ<7uAnYbM!1p>t>i zC68k6Ma?(qsh`F<@>yJ1SZ7rtH+3RmbgaO~TgOofC^-~E7QBh3H_}`>P@jsj4&I}$ zQuN$!N+TgUsN^uVfU&SU3-;$tB*eL^5@J6~=qTk${Y z!8hT}tFfa^`Qs`Mn(uawHr_nPp!2eZ%)cwln*WrH!O--MYz7g(*VqZKPygon!%o^u z>;d>Ue})V@BX#gk`=4_!UkCpKw089J>YL_ym``!1Xdb^J@=s?Jos*94TNqmuGNSnH z&XZ(4{S&|NUyuAJKKT5p$Nne(BQpMaoquWR-_x~^@eX@!Xb}8Sy<_+_g4+!6wijC^9oAqN`+Ti$o zOBEk*(HY{)X3+S6C&?I5&&Lg)g3UK~6kEXjmGdtM?n1!F?{n#_^ObizKVbe+(w@Qg z{mKCGm9HPbl=X{?=LdwZ;`$5Qp3h^(#WDbVgr(yCg>45Rec~GdhDrCgLEvH=9`Mnb zn?b4IjO;YZi(?N$Qp`cvVH*u%FHZYcy-t|dH*guZ^N%r_XEgA|@%lj+4Viy&z5bTZ z9AM3(Ft46vfcq^^w)h2Xal8LZGd7k%a7eZSSceMk6nwrp3u14&wc<*ZoD;iAnb_I!=tC^TyLP#Rq@nuY8{9nDn++j2`j1!p2y=VDx~`6E+1S z<)h<0;}Rd)t(k9(rJ95tI(zacRde4VKC+Xp=Fe+iEu1y*g@#Ly2RLbu8z@%Ul4MtvkgQ%N*KdpJf4U^23ti=|!93 zBfk@nLy)T=i;zWpd8{e%<*}w*(bG?oe#V49-6innvtsviCP0epML8&Qu_9-ovpi#< zPofN?(7q~aWmNVhg9re&=p3beV&%*9ZRJPK(XrXJhplWl(JA=KdmTDUKdL=BkGQfN zTSZTEEZ0S~uhua>g_5sgvjT%QH|uns#kf(`R<%0j4qD8wz@_)IJ?JSvs*ZKgUo!__ zBjdo%Bpiej^C*vN=7>Z4Nse8YnC}TA`6XMBuGgUoW=l@}`j9xYRs2p`gBoKe zUO{n5oJe~?@|ib5mT>ycOJ27SaGK{5?Dw&cAY=$QlhS4&`-fu;7^gw@=mq;(_7Q}X zHiO8|it`=Vg1Qcy*aE^y+?CJFfNWN9=hG%}W{?g26~tPrHpsr(c33C==-#D*Lmg`) zv>nDLILAR@#j7^@un#B+j^B-}?wBy?{9% z)Y?mXzH&#wQog5?b^}}cfN>gRk6*wa$NPCYhbCR0pbLakid|VZKkIKWkE)cM`p#3b z830bgF^NmYi@ua!9|7S^F(x|+oOIvfdA$O_IcSGX*8^w9D^{O4Gdh-9ZLe5;;B<|V zV*pmyIp7hejV13JWMz7Hb1UNKdhr{J~a zw+2p{NAZS&&k8$y^+}&u$TH6wPSR69Ve7N9<5WjKRxIZ5bG?bKTsO%=x6wt?SVtsC zijrIPgBqo2Q4${gC{k7WVvPP8c=@=NiNUy8REausAhWD#5L=@hffBdhw*#bDypXd4D8ncfIgWPKeTIJ88yS>5N>MAMqds5z!HOW*q%)dq zm-i#;75=T}kq+bN%pB3jS7~R_m$b9zR1X`3nQT?yv}#+~#5m&GHeM?`Qb%(JtZdfO zVIFank#=h0rg6GW=)B@q{kg&~edgWZ*0G~avAecm;gk4J>bzLBF)nQ}U**BJuG)i* zj8{7r^RlXKt2*X*#!dd%ChAEG|F$tZ9BX_2V7@Qh=5!=^7W?s3Ch4AE_E+U13n{ zx|-(#gNi-tVqQ%=VPjY92g*5dpxCY%rw7i;dlhpt9_ClY6f*XVV&BXCQ9ZGn*Cy~i z5H#mxPvNC%j2?Vv72Cre=GVld*snR(h5w3sG0e-lnClov2-pdC#eEFgf#*7L5is^i zxXFGPoCl0O1AFD`5OWe^?4!UtjFMEbw(Id=fO0d8!qJ6cn{1N(Fl~ozAJ`k=jMQ@@ zl+R1>I)UE@~#dgfoT;Whs}<5qlA{?lG@g+ZC`YM%7vIKSDCorFC)e==_Hvn@As zq>q~C3EOrfVVmw9vQF@@T_3#GviSud=H?| z`xjb|ax6RsI>t_YG=|0waK9gEe)eY-KFd-}OyC19`0j(war(p4#X@I6%%ab@;1eeWnR9^o$UrD}HMIHQ_9r+w0IzJ@ukJ!&cDTGpB>>LzVF7IcHR!*k(}nRNJyX>DdYNp7yGR zZS_55-l{(c9dkqy_UMe{wec<4c91r8TkJ#P&+7et+HBj8jaqXRA9OwU&ADnru^p&g z8y|U9A4EMW{J=U81YX@n1o+MK2YyF%-$Aq^z)xpoPY_tGY=n&8y#B&(D7i!NgpA)H z`|vYt#a$4(5b>AqKY=sQGhdB6B>bcBQNI62o$@G@^Em|k#B~`v*$)dl*$ogsahzs* zs*Evy!aQP!TaBSzuO9<3&Yw`u6gTnms<@fsVQeFvpZb~*b~;maeIV@!_$Tbc4UC_# zH|qTx_50z&2gtCm&VMNVe-m!Zux}^G-0a zeJ5k3%{0#mTf*M}O4a*T#!v%4*%;ulQA_xybBbaaMxnJILn%^WEZmlTG=|2~9M9(p z+b$OI(V~8QZfF9Z&+3I7PplAKkhz5UQKX`gFh8196p0)@NQBQLLE;5zkX#|0J=_DzY8JXJ{GG26gr$|3rk6TMbTQ%~KhJqK-r#=|em9JS$}Wr5o8 z&(e&uTgDAMCJ+6qC3rIMAuW79D`6x^<`M?|F&pq|&>yn_&j$T58+;zlApG<>tsPP5 zunO~YS|%0BKggXvltGjX3gwiUb0n2vRjU|FHYgWIX`V$PJ@tono~Z!gCn^2qA+7M3 zI(3d!o$#V$tJ$#Vqwy&#dwz9f7sWB2YW`ryKrwmnD2J=rchd*|UU;_6PuRQd~Yc6WrOC<#@?i}6ythOe4UdvzSR5-oV!{+ z6>f*^#`@JOKJ+{at<6430zc(t=)S`Aq1)Et3w2+xFC9aeZN+CQOh05>@fBK*=rJfh zLtVQ%#b=(M@EytZR^c+L^)s)3h%dA=Q_Yt;#An{W07G8I5mr6o_{{rH#1iT?LFFpq z_)_E#{s&bIVb&vzuYBK$crszeq}q-sz6|>z3ICyA7dS@t%k2xo8^~`)bR^X255zRLw`-HZ!H` zo2A{3FO+8obQZbFGq2<8uznO<#w%X8^Xd>E#nz1X*PM?OTc6u?@H)N@>(|WhZ}SSD z&G>vmeGk}-&zF+Cek8G{=q%-Z8H!|oVf&$2*cVIaQepd5e1*cFf`Ey_#xsE5_k=48;nYsdM5N^XZ;BU(Jq1AB|5tvF~*Z?DwD;D2}mJ z^Cj(NA&Wi=%}-YIaaBa`ZhqTjY=gW;Awo)jL*Hg!1fTbIVhtjx;@uz-4|?*1Yx&|jjZmo zS{rr(%{A2*Y}4Lw&6$TeY1OY?J?K~or|JvstOx8~_{pn&t?FUKs>Unrd%^B?PRc4y z(g6$Y*HF$7_6hq2idXDj=Gi{S297|^CaZXqc(YdhA?iliw<_UpI7FZ2{TrqaJKJiU zRL8iK-PoY?>@z5PZtSpcmYAD&GvCWt#g~^o=xYx9CJF43oFDAd3s-*UD8A`lF_iOI z$Ef)9wN|=aCvC-dBx_5U6rWMYTRxwGBkzQtAjZ@&Ui12k7zTof!>Nrr#ydd1fivS2 zYY^k=6mR+d88Jk1PBB+?iZ@Mufq~xZMS5=3A>ORwbFj(drQ9t)|3!VIiQ}bME1qAW zoo&*1C&eoFZ=`#dxK;L2SiUJ|%Q?*cnlN6<)sz~aUXMDLLs(ymk?w^n_Ng9cyWJWL zG(kRxb>;Ao#UnREwHt7VylkvQmoBe2w$T6Gu zB#4>rtyvektkeD-Q~YLHAl{4;??4O0JHYXhM7;T^VrIWl`J>p_Crjw)Jx1tzmwYHb zBk@PEu}_xJ^`Jy@ZYe&ah?iord+#5~8b=(j!TziyxCnh8vtRfMdwnPETlUQo7*b*5 zP<(|QZ;E|YlZbaX%$V7Cm_8^5_ESq@ES+7WVc}u_VcD11EzcFqJ(l@v#*fi zCCo|o;YMN}y_X0)X2uXgyoA+!PryFh$}nPP3<1aMJ5~i(pzEyI4eERjELH^@?dQ=T z)3Yyo}B3eCa#IcA}E-YoO5Hag~>h z&+R{HBPdyva%_xS)yXk5mge}nXJGqQvRItJZzcAQ7mJ0i3DE_Ke?(#&zn9om z5Mms^r`VOlpWDLkEp{0~^x^jyOVTiZS+fTZE9jRtd+>XYQz7a1AS>hw*&f$qLAG-? z(vW&7dsvE+p7 zJJ4o(W3rFNB%Rs~dK`SGQ7Bfz!`LQCCw?)%2PIR@j!hr=?{nhA?-=-@bs0vf8ZY|G z9MtlOvBaTcd#Y`v9@@Pte0W_K9BcM3*!8m3WnPuoNjFM3?Y+70fj!z=p4Y|BBWwm4 zH}erl{J=)}okd~0o}^Xmuxa?UTV?*Jl~1QS*d0Leyw)l%-6#4|YJW&|Px~KOD91gm zvDG|_*z;E7LeRN7|KUFqGzK+x*bXz-_4OK2eOIf85jXLu`b0abHD?}@s(-6GFa?_D z6f+n0hc=xdWqm~TLH2JY!^Xru;U^POyehw0_ODA|q3c*6=eb>6>=XTT?NxC_Q6FRf zx&)SN*m&4y*naHnvwu}$Y-oF2#lb#RU6gh9&l0|SP(sahj!*F!MUGGM!TwnSSB9~L zEft@WaE0xg?6ZH8Xw&%?84mUxrBB6I#aRdau+kSg2X&6U;u?pzBiZN7Z=GW=-`fCB znlVKs>l}Mh@jnt(%k+h~#4D{@`N9{FA;u9^Rcc%KxvjD0jB{}p>V!q^S=rOSH69nrey;U}fXtG1gk zc8b|hW3;O`N$j+5GbjUgaklks2AF{+$@z+Fh?AHoekbF?7!wEX!-&g?&6vdA?DsZS z`(ae(d@J13>hYUDlS6*C%6l0#enYN%%E|r=dQ6@7drI*?7IbX2>?yw0^@yUbKckI& z&(|?_bFVA@)3w$q=zhg_6l<;eNV9L2h%M5*SNy8D!md~3yMkdj=(Vk8Pw^XeysG_S z_Nz+ZOjg4hbRYW;+HMVdN%pHt%*)i6t8(b8&c37aN3pR_D~bMQ{TVc!W##AcRTMZq;+aB{#nFoN?(AIOzNf!& zVM+Nr@~eu)Rf(d}1v%ciN+ckSPW*oQD*Uc_Cw^ajUU5Qnjtemhzl+{kAXyNdIU&}; z@2HcUir-o9G)UqPC_{GOciKAzBy5-xHYy}+neBombF!^VL+YiR%2Jf{>qt3E%6U@K zufUTZz#)myOxcb2%vV{;aUE`@QLbBHr;gvsckN;JX4=(YoNb{#VgD;kyFNb;EZBnCpn|3VeSM zucGe=NTC=g_56+i%5jysI*f2^KrM%B%eN5`9zYk&8O;wB7%Jk``)c{bc*-5OaqB)uVI#DBfbAqo z7De}4vt2EI*rPSCX~V^sbTxl&`{0-Ioc5Y$jCMX@Gvmi@mARHb)3C!~r-ywyhsLV- zam0z0eb@|o-E}N!E5A;4z~^htRnByu=%f9v;|-(j`aA$G%J*!L_|*8N{d|!2tJoS< zeb=f>Y;=wWx)!Qm*bY1Ib?j-?zg<17*ojNkC)&Qx6SC=|Jxb=abu4}9GjBl|>~ z&QmwogBn})8~~T?EN~ee@2a6 z#y{F3>u>0>%lIR|?la$vhua~^JnW{}mo7u{>4;Ot5)y_n*rl_rhy7T|kbKuGe%i|! z6!X5$D&DrP&49{4ljXmoeOJ$zRo}c0s;-slF^Rub?tiTI2E{&7ubhj)`cv$!_WnoB z1ByM>!t0-+9BkG2mHk(jGeGgL=44d$5ygMH9yy@+k1A%>Pg?QciqF3*-WOv1Gg0S& z!oRvMQPi!T1NF5~{C&*5DiMD+N)Fg}ls+j&_NzfJ@UZ`= z{84P|(@tVcvwr?%jAFBkTh(LS07~fRF#C+c7sbT>oJfoheIGE62;#4q_l!lAz!mEC zfPIA=zwLR?{_P}i(OEDW7C!bHmVJrc(Vk~4x`d6;IAC8P#ZT+zD|Q86C>$vEgFb5m zi(SD;90YnED7J%)pD+fp?u<{BupQWSf$hR(Q1N>@=NX4n3A_U+V}5b7KfiV)7FXAI z_U%=|C+!Dcxxl`C#!nbR%Xh|TC*hmkGkH1}*rvz$$!C&n)LQ~eA4}{mB5@rF=Py*kmXp}ma0RY#t?TjC&gpJ zlK7ScQ7m*qjN|(u1Y|CO?N*=CuOZI*^?OG$Ipg6~2w+bp|eyIaa$ zDf^`yDoKjB@+JErO8A^gqY{sb&8+7nK9+@2yNjjdqnoAVsaNvWFZmo|Njw|)0tn(A z|6{pGA|9-Zfn}m~GO%p4ZU!wQ*3n#6tgB&$J)STy46lTNf9QK6K8f;elzM(o1l*Tj zl=mouD8ndOlst;LZ_0yQR4|v%q>OG)+9n_5YXGIkh3Qa-0!tEwc%bvZjNeYW0RkIj zm$;~E)1sf|^ttgB$RYISQ8XxYzEv3!j+(qPrjYSuU_Z&es>zUX)Z~NxGe&L)B=fLC zT+nlZs`u*(k40)lm$@hBuwGlI3h?A_}n1YxeGhT?3 zOb~JTwGlI3=)SN0zH#z7LY%apGfd_sMfvM@4+JqkPWwo~(teJ)I%h@{Uh00vC|HU? z6ziAli`M0{Rt3@{E= zf|q=~xM3Xi@=vid23?{rT`ydWrC$CicE(^O(O;LGVJvmx#Wq(ITy;5T6#rh&20!yG z&lSeR5^I)?dfgP?e&X{oH_N_jNpM2T3 zjG3r#GOnonQEcqft;F0Ol+h@#GPWpu$(VfoJP9@;;U+>}Xq|m=qF^O%LOq8SUtX>$ zPUlbzfpDVWrE64ZdrI-+G)A}M2z!$K1SyH@u()BIuaR*^bVoI{jdP(3I zK*{@wiLv{!Au$ZFt++_wqP^vbACAEzFXS`LHsUIQk@9E6O^h6in>o@)?DB(Sv6H|} z=g*+TUxc(RY@^y3*++bn%_Prhbct9}GNue<7A22ja>cQ6TlUeI3<~j(;xSQ3e05m? zUyerKlZG!tTP6Beh^|t7h3GGs-(3fNLG1I&9pwx3h>J%62 zsZKA#PqEXB>T}InseX=9r#R?^G4#EF<_mR`b84V2*Mh!Uj=p{lT*;R*-kc}L`>q?X zK$z{~)%7_!o?=()1*xA2sn*A(elDb1=XE>>dbLh*5+0H=A55;)da*R7lg3akVSFiX zb7sBU)ECTpiBCAjn0`}_e^4)R3!{Db{y$R>Lw)#a#!|h2I(=;;@xKD~Az5D~`tWsu zC4I5b*ITO7_x~M$auZ5izW)zU+OG&0pfekV?yU?Ix}MQ_K8iwV>gxp?Jt$? zF}lSC+mw%fc+|0FVJ|kk0t@XQ+Dkf4M{PHJ!p8uLtMzh$%l5l*++g%N7C4E=ybHXR z{5tgyKXiUr+6Yijc@gQ^3V)O%zIkL0obZXU86F>jq}Xsf7VZl^=={$H9ZxOP@E0YQ zMHwG)8d{#hHtp?D&aG;EO1~Uq)N-^MHZOg&MkpDS&|ph(><59!st{NC6~mljte?EVWCp@7i$!H6&L(vqwwj` zN6q_t*iC!1t*@Quw#H{&&K##4s95znY}D<{RPn30RDB%lDptJ?ys_9@Dy}$ksA00a z#$(KYHqP^Y^=qd|_{{s|A*lI{#HWgt>)@g8*C2H*7#B%ZAIG|imFvJ6o7^PM7#GPn za;RXMbiahYSmkDe@j2;!9{B6_K1f;Tifdm3_8paB)?be`9`igrcU>L3w zj;b6r2%lDaJ(lm~8idcJhBKCXo%V}{vuZBaX@6Percoy>HTj7-pR}hd&P~D#UAE1q zr{~lI>UwiMu3Qo>#z-<|I1LpW*MTt=Gn^G?0OKSXBb<}YOvVX}=@{Xx;F54L&KU7& z%caC5kv5+-`5XmDeOCPGI*g0$wVl>k#aoAUr~4s8#Zaq`@x=Y4 zb1N=76@P@7^6)i?GU?t@#bZ^E5TmCxQ!$5#mGY4GDP2n(?Q0cRyWVD?%|L515aOBS z>AckOsH>v)#jQGcWVA{r)|e{h5_~ zri03*Fzhr?ewx3F1P)SR@DP;E2I6y&u?7`;7U);_kIwN~jjA|Wy={BFPt|8`oB9%kE4nFg7;#9>K zCtMOsQpIOg$GBMKsERRWd{+IOh=w~hIjZ4`%lS#?W>Upgt&Xt+amfW0XIwA=hoRzf zQpcQZ9Pq1H=@Np!uAFGd6dAfJ!%Zq>hMkXQlZ|Ts{Y|C%=c0T^{c21h@@c4k;!sDdwEt<11I-~dwxDfGymi~(YHW6OV5Db#`JiG~{RCxG;!CTx z<6Z}5Is<(3s>T@rpL8Y}D6u#v)!3ZYft`5ud>^3tfgP`B39T#PXjmRq9;l93=>6(2 zimU6EY6o-O@JW6$D6u{t)%d*DDMl39cUhFG9H~BPjg|Ij%~I;TR;yF|D6}7m;}I0% zc@(9nazSmWr+z9lp611dKmJ>QMf{OPsvE_k=q%M&n19i(RL_}zgt6ZogFj$bz+V-l zI{s>1F^9jDNA+>k4Iw(I4jnO(pgMH)hxVvWlK#-0K%Jyn9~Ag=c%?tsCs2nObmP@$ zx+&LltnV@1n12vZpiV;z^iO4-T(RDCQy@i8$)#DBE+^G{N-j-(W!+Rsy;{!+yi7%p z`S%M;^}dR_Q64jB{;kK-7_~mWS`V(wR{{Tm&vG3+m+Re19cxk64=Ht%O!O46M5ya) zU20eA%XYw3P<4Y073iOy)iGjy4N?{8pUQfUR0aA+<#15pbFkEHj#2C5%GxP)@JT6V zPEzaOwwxQ_R;`!w2RxT^3_MrV4QaC<`kbr}pq5>@ z@1`-dUr_Hi>p8PNWY%G*05fJClM66o)`6&isik@j^&webAqp5$(ie?FzgefhE15=t z<%avak~lZ%`9TVW&e04CovCzPFDgYQE&>eT1-o=D9YmpXK98dMQtQ;0MImf`0l2Qa z3B*Bs)AcHYLh%)Al^{UF2l*w=9r?7uM01Bxl0jgK$_DT<=ag?X`PRqy!6|)H{_11*20Lfvr7vncF8moPR=;)lt;-&- zh8vi&F8J`A13r8ttKoOj2j6wQw%K6>uCxzcqFRfL9eyYJdUH?w+TlcBz5)2BbxzvF zP)(0{bKG?vs$bc{5R6s!+B!paags%L+oPHaRTFTwe^Z=UsjJpZv!_yQ;6(+ zty7*XaAuv(jnTN8*8pIRWn32g^PX-fsmGzV!Tn&2a;1V-6(`l>inA6jJq~VTUM$ZY zj)!rwWPPJa#;;4n5$k(t3%nKkqfypDkAvF4o{Bqu9e3RE+TqjVko;%u#>YjkT`~h<)x;j25^X@%))||B2XQpZiV3f5rPR#MMW!H9~2U{5RA%z1j2`&%6^w=s^! zR*f}|{fwVwj4)C@CmB~Q86(`TazKwO2650ipVD#FYR3S#E8ZEe9vAEDAT#jkns>%u z2fUv6)^Rd^mU@4P?JR5$qYR)}-cvHBwrn%dW}wYLn}Ie1b_|4goeJbSYllyd$8KC( zAI^KbHvi%9>NH=Qi2wHUhx$E#A@1q@M5t$V81|ba|B3rd82p57vq|_T?%O|qso(x5 z?o(msY8dvLB>#!~_Rrtzm;dJTJFG!Ij2sQqW)tLp`SZ8nso}1{O~8NoXT#t>9cHeE zVY>EiAHO)x`WTk2l#bh~P3si*eA02p9WQaJW43E!Z0z@d zj=cf+PwAMewGmIO-;e3oW6uAi_ggxCy^VO1D6#$7um#?*&TPMSiNmCh*GU`m22uR_ z4`sfNKdv~5LmjJ|Hs)vJ7@HnL95E7iI!?E3*ofshq{k6M{ym>N7O@S)Ej|m3p`3F* z;vUd3`e_4);R8$H+~~2y2^(>x;|rn&SoP+;hw%5qDj# z$tnhkBk+4H2WuMIwN{U>S{pbczcQTdfI2KwGg zkHu>n*yveBHVQ6uj8XWMu_bliaccwTAWEovL613P{wWq;&oOnpVYh*o_QFV5yy*DD z@*`tN>i!y~4g4vTz|SN-{t);l%qiVZqqc)y2c3ri`EM%TEqLk!R~f&2S=cT5v`Luy zbi2*kMy&K4DUIUk8lm$7J6`xF--IEO=QcX#xU?yL$phtJ9;GIiI(8>>q;2xi@|^4Z zscrB>>p|CT1BLj?qR>5Hd0o`;R9E}tSeip~$p+bqWsVo|T9GIgbAoEcqS0AgB}6Y( zCq!p)g%Dj-A4h#$;2*ql5Cw!xby69q57BVcQ5F5Lh&s%eh6-Horm3P6u z2l%&C?<6y#6Rz+tnUVDbc^91p{39ysr0UG!9{?i3bwjD6TGlId6QLY$qOz!)2&G=F zmk{R2no#QnQpoXYJ-1q)u&W!Z$8_4%yA*vl4VS)pZR-7s-dvp0_=Hj~Em}$6t?2ue zIxV%km*Ur~GACi^4KiBdJmI9CTRpxs%i`a{>M>nvU0LgHwXWnwuUc2~s$Z=uxi@4I zUBW;+ieoXi2(Q=b+{PXxK<0St&2k-EiU65<)Cq)H?=`n(sosx8WIe5?Q5u8&U)q~F zw5eVw>CLSVLu99y^oW4!{Zxk`s+(0h6Yzhjj=j6LqZU%=MDo zObB71hy?wgq+0tGLk4jDO2!aYLqZ}hNX8?sT5tclP zjz?`%e-?%C)4X zbS@x%l~>2>t#cmOLTiR-eNZwep0VjzE!vb*Db01$HW913=0WTw^HwlO!X(CFi?s>_ zuZ`U-^i7)wh^-GLl)V)2+S557=6WFg` z;@&fk0OwNv4Y-XVPUC@});%8}C${V2H*R30vndj+F8LVt8(Y-#5nn25F8#y^to1(c zZSz{@N)|Hq*8^Zoq7c?v@dV!Qb^U>r);@4NA=n&+&DiDuuo)r5?08+gtUvHJ%Gxsz zvC9E)7>nzs+w}+TR6X<6hwrT0Si>5R80xy`RSw<6hG7cak2ix9{G+H(8AXH+@+5Oe3v5VYQ17ee|qH=c*aogLWLa(SxgoTl*U8-z@L!XdPtSxUPxL&oVfTs6`Zw%( z=5Z}N{agsX8vg#+X8kiBd2G~u;QCn*_%WJ+Bb)WlHbZPKV#r(ZV5fuKhQ8*jyjaDE z{)WD$sqs0jTk&A0gU!5CyKx_96=U2nhk?(E2P++XH^sTn9NNU#sMm4NxUG25b>J+? zyfur|W2APo#O5XrD;{(m>^0qeksc$p-SE~dW36~lb%-<7EU~I_s4;R~?QfMj$8IOV zL(|;zscUK#uiD=#bt@iN2M*HB$^*x!%4UtVN?cXn|6^WL?tS7zU3acm;mPy;b!&X-y(I(pZ_8DrvD6#y6#+Wku_JZWtP7W z0vzX7uk7<0q*0)wnR$SGZ6O(?r#s{+Q%t1`z3U$5p7&&%T>SJQmroB%0=)TT* zHCDAAqc|hW0X2TE$ApJ5;K-uTdZ$s6jIS-*473?&Gtg$B%|M%hHUn)2+6)B20PZm( zyYG&RFDu^Sf-OoM#6y(+;^M2l{-r(NUjNmZ5SQGH5=VRe+v|^fqvuU6bq}z5)mPVA zjRRVDx&Pbi42Uv6ScL5>WpZ{yk{cp(gKh@s)(>= zpQQDt7uKJC?%Xo>0Hvra|81rCX}#6x(H?1~_|@3C-b(S)dUM<)(KcEsel>Qkw^IDH z-W)ecv{S7VzZyH&TPc28Z;qQKbS-laz%i?`h1S2~+Rq#y{+781;Fwj}LhD~~?Pm@U zf4WsT;Fwj}DzVc#t8sH3`u6w#wZ?nk9$s@@A_rRH8lcBb?H1YpmS;cnL1#lN@WAn^ zvK8W{^;Kim>*#NP|9A6V1L*yo?b*+{fH4`ArhXR8v8uA!W2SXgia;On45Y1(^fH8(;)})&3Xn{#oes2 z(mGnjTT6#|O?w7d#oom0pSFtEiH>ro8F{gay&2=A^|OlAsSfi~&B%*Y{LL6At)Eq_ zZgjAbZ8mPK;%~MXY2B>ibgP4%WV3N&#X+;hNb6=5rxzV;8O_9x6$i}}AFY>FjDB>m z*);cpRy;J(`un=)v*Lzy;HPQs1+8LiqV-Q%#TSGQ95uytz>13|S%1SSwjg!jX|&O~ zvf`uBW1@AjiYsUx{3KE6XUWAzEmoW~YAg~rtw+)-t}t}))zJ3>R-82W`lqa73R4HZ z(v8ZQ6)%k%3$24yJW=T2JJX1~S#i^daS;Dju|%nB#2m2VrvcZ$?rYCHg1d(L83E%~ zWrMAMAkX&I`2Ezu9er-NVcr*7#o9pYPdwNB9IsW3Vd`WKG{iYz#Y2OvKk*y8wO9EB zzS!Rrs(88H0P7!{>pgR3C*z(2R=8un{tdJC%q{qg?ehV~sLB|xe_F-aNOf>Z&k1Aq ztk{af7_L9@ma@XyNIJxjM2XdV11tVww*JId(h6sz>JX0+7mlqsj0-m6$qHXH=nxzI zZehOOTw8HjZ%mSxlohsS(;?Qno&{EX)_MI6D@@I-1K)ICP?zULR=n0}{VD#W6`oen zA^sFfq}K#1ZX;fQiZ^A2qg8d_BOO&9SaBTD`qR3nt^BujB?e?3(79L)t5|UzmM>|S z;z?WiZR@NV0AKW8A%hb5^CK&d@Dp^t5pKfo_Dn-Y637}^aT=6u ziIecg`sZ|39K>BmoT6|pcs>`b;)GqVxkWfAH`{A&MQn&VaGpXjB>#@{!3q*=I9-2U zZ}OY6venkrhXJfdlGX%;)@B$bD@(=NTA{1>4_-qUPqJ$j{c8d+YuxTFmVk7ba0CG|fp8lU znn1cttqCwkcDOC$6kX;h!chXkZ7@Q^MOOj;;MG|y0qH7Gi`pf}T?J}UfF(y=IkSm& z$*_I9U=Cc}Uz%v&Hnj0(Q>-4ZwIy%me!VSuEBEVd$y>Q!Z!>SXlHY{Z?krT|snupb zcCuUg;eNf{YgGE{uhAaT`b(>g_0E}g5?Z@cYj5D)RYRazojy z*f%c^1}RGO&Fhig&aLhTTt44TZ7;P=0Hyi;)HWF`wTGx}GFWO;vjo6wo9;^tm~GR1 zu~>jZvn?-|0g?|t(YICb1KCM)OA++;nlH?vQ0nnD>w89{NfCgnA%7cam$fRb^G7vsy=E$S3I z*&L`>T#f7(y!D|_9%WIg@m#YnN*;if&LujN#{A-TFrR#ouVj=lHkvQ+L%Bpe1Xcy&WVEh=J|_>kbed=@K4u?Y#j25G5F;Hb{hGd;+$gHP`L%}>6$mpYZ)i$C$A7k zGv!o6V%_#YoqX5rUJLQkwPB=Aar;_lHE(Lw5nsL$WAur+F4sd~rkon}iQQLj z`0-sUm$)B33_pGvd~T%AwZK&$N<;5q&YNlyG1A_leOtHukAd%GHI9PnL!6XT4bQa` zJ`iUspH;ktBx9^?bY8FbvP?N#Ffu_l`>-T>kW?B_ecLOIox`?f}2 z4F<3du5&%+J_EadN1P)ukJasLj(Fr zR^zQ!AL1Qoz?fa-vKgKk4~- z19)x9`*b~jtK?NiAKPx1pIc<@c&nxde@%T)rRIkn4;YhG$F=L_oTBTH>(6CjPD|Za zRLQHRzX$NMSL}GR(!&our^@^B*JyLCcpzP)#St}LJKn7Hz}7O?KVAnb9!SUiZCP>s zqidj9?0D1l7*Fs2%JW*KuJgd{R*BzhoXk@#3Hw>EJ{ozR9WRX>Qxw?kcvJPjk&XgO z9DS%fsP#BvjT0u7E2}!Nw%onCx;|E%kWL-bu1~%{vg3{Q;LK=c-k1knwlXJ9V%Pan z+n7&hRZBmoQTb5oPPlF1F;zd)0A5=9tfIV7b4jhYaLzb+CSdz>9PrliXH`3|Cf_$X z+)uVYcgFmdKdah#e;T=!D-%MZaO&^8+MG!iCS-SQ#I$3Dpyp{qsul|Wv;4z&vu)qGEe24B2M&t z;&z)aldmlEVI{#)s?Ae7uUzIp)$1SS8SJ(1tt{@@iD#>QC^@S**IceiGtBL?)Q;p8E7-mW}wYLn}Ie1Z3fy5v>9kK&}N{` zK#&aJJ!swD!#9F&JFXi6dmCeXbGw~a@m-rn-Z$;MYUfoL`PKR# zD^IyWIYmE5YUh;=2kpG_lpB;&nRZ^;bJxx*dv2=a4dqn(_axPq{%km1^geJ$LQAvgf8s-lT}PD*Y{gjuaPPPWic&&KdG*xV;Z6_o2=a zx7$2b@@*IzQZQbF`Wx${FURn#8zlo3|2gc09HDDd*LsXQ5R% zOSgGz=as`8NZaL+l^!*CURr!6Ndgjx0 z%+p!Le75?T#XRV;#m_54=fiDVo>}=;<R^=is^(`F z?6|6>2bc8Rp{4HO?Ko+%ybA1Iy$YYHR^qe@55*SZA!Wxykb3ahGS@>p-dZT9Lc9K1 zaSL8YTY+1v*h$w4vC|sc#UGX)KI#1-Jwz7A%^tU0+fd&B*>TEx_#S9>emPcM zHhbK(wtCD_w&B02@3ri>Z1TK{^q#5;$0N%zv}w7RRH@CdjLX%PC$1nOhBgFVC^)vcdC; z)+E;VQ##*z8~7Xg8H64Ww;MRGMrlnrjz*R;w?-OqE$n!2#8`lt)}SeJN?jMkG~8(G zq2gETjTQ%Sui|gEI^v_BRgX2wy4dmGDEKAU19ovVlOD0s^V88rSQ|Uu8v(oIxS{XQ z?f9>zN8GW$F52O5fV^tz>z>`Zupa9}pO=sAvk1ni%NXYsahcNbwqhGR(6fkGo?Uk9 z5!1XP4x8@%p4}Qa)q@kd@5;t$&Frwp38Un!f7!^y}>ntEcAoDetdYo6T#)zA}nD0Ry(J9xlUk6foc*3Kup zMe(3l@{7*?$kx;jYLpn@i(+o)ligwjqzA`olq^c<`DF(=3g0ruv>k@FJ{$(XdlH4- zTho3HCCBWLhviSkLNO)ne7E({F@SZTXB2_wm|X<$6=WXMz98)FX9aeI$3Rc(gEEMc zMe()%cH#K7E3pyAb}reG9CJO^E`>sCID$fFgX3JY3(buU>63gDhLoMnw!ZNhp!G$e z`+ox^gOWuVMXAU+yGZqMaxU2*TV#`Lr|jl8yS`X@O^6{>4AEOGPKc5UEmS6i=v=K^ zAxKvMDMRIPIa6mq*UP$9XcUX|ujpbOra}jEWKjCZnYwY(3D%i)(*>NA#+j3f#T<2s z{*sQ&!Ja`v^p3?WD-amnFA%!E$js?bSX(u5E*Fiv8q(3PNw3SF+` zSGp2n3g#LPb){jVJQuWaUBaXh3>g%aUzR!ne*l0EBvX*F*0-k)o69$4!ppE>7PoI~f7w~tyW)2mdJs*Gr2^`(S6vCk)+r*!?* zIiTybmCh)eE>-9npVM@7FJPrBXybY@&PvCmGUk4aW3pH`1Rd8|LSd=nfaSOZjpN{? zu5^!NlmJRy>0Zi8m#Y{@=TPY`4Qe@;&Y|)>gVgn#IvI%6(b+`jj`@;;+U0wdk}grw zNzf%7-zOT%+%7q;py+y~uEc6-Zokx(z?5`DQdeTj)WNNZMY3h;;8yBP$EFT$rOtG0 z>UzsM(}}4YD(lRq`43cbt{ENa$OY?iXp^p6(UC#Y4JkT84ILpa&!r@U&Ya5jD3PEW zsu+hGv( z4>;r6|NQ;Y`5XUsqlaJn^|foJb~yhB^S9n|Ph;kTzj@EWdx%%XH~yZOHZ!qf$F}cD zexUECPyTND!i&@z6K^bLMlS-Os;#{o)Vrz3ib=?t1zQcTan~ z_kk|s@2}sv;gV|#S1(@v{L6#OhEMs*&z>2*Z1K`97SHLq^0HOi9ND|UAO3Rvf4}?8 zW>Zq>jV^!p$#YKK>G#8*9X|E2q3ho>V|dA&uH822n_?V$<{{rc;PCk`uh~Do)2y}r zoxWKFmo_^(;f8RuW=Zfz=l=|a0`VX4>KksaK z%vtN4zvtfh`Tw2qgQee@^YtmIeQx~K$jDm@zWmR(x4+_)556>K>x=K5lfLNjj>O=i zrCa>_mUn;i#=yWDV|zR?d+UquS(3c_jh%n=n;voY@0K0?ftU8b^*_6R?6U1XdjEM} zz4)b9o_hJX59}&VJnE_C8!Wkf#T9RV?KiXk{?B8+cEgg-4DOprZ`+Yx`$OlhJ9WvN zyWUv3#kBL*-tV051MvpY{a_xssHw;M=oc7CA@n~z;~-VsxbzdUu{0YCqF z*NOXYz1HpXKQ{gH{Pv$dcg^(eE3?1)=dV|7b-qYu4veR0r&nIHLF_mYLP zHy0=W_#gLQHL>GAzPTj1*PUOz;xK`rez@eHpLu=j8Ar^RzvY^j-M47=Pp9;4TD;`X ze?5PfgU&qU+E4Ag&oO&ny2l#n-);NWf1lZH!AZ%Brf<7a^c}lm=%Q^W20#3{^*h!Z zeQw9kKbg4ndkCWA{eC!j~cdy#_k*^oN@a+W)Uz}q+{J!4H zdw+P-XL>%l{F-mi5m!x2JNSxCdiE9DU9({6KHa;0Y~YKJKDb#&Zeq9VPr7T-K_?x( z=E4HVdEn^RKe+u4+gv+y#?IoA_bvP1v9F0O5C7%^`)+umxa!EKf3tkImv)~u=fYp^b_AS#|4Xl5KXTGJ`HvU26dUdEmRRuof2V$6>%`+{ z6z84v%C7mf518L^#PjEkOg9caYvHl~SaQ@0iS%2$UOkc9XZPM?r(8SLc8mZ@g(rvtT>#TL}JS1`0|9kh` z!oj=GKl!)QHz&IH+P`o4+|KXb*LU)gldtO%H}t2!`M)<9mrO70f8yJT!JJQR#e{#zoc6#a%tom#7PkA@-w+p^>`>8*=T>NI)2QEM59}j&Y z@#mMGe&n9(Po1AQ?4r|`Ck}i6P1oO$`+XvP&AQkAx9jPRPXB$+D?jR3^jg=7uf6&7 z$LEMg?)%>3D>^4Oob}E+yZ!n=@zi6Rb^dwTXHWjgsp6{JE_~&x4|fgz?3Js&^5B7q z^kcJs`k$_EJhSva3p&2Cxv|zAXdJ!h%00)s*Acg`N?rQ-`H8R0q1qW#>o9KV@44~( z7oOi0)XuzTW#>^F%{*(X!sFA81w+XbyB_`h{hi6JFTUf?%e!{_-qrWq`@P-gh^JPi zX1?`-Z?k%&Vl0AcNgCM%Eyj9eX6n3VYzo#r4D=eotZQK z`s0obFTSlmx&7IP-@SZIam`Tb+S4BU`)ydGHJ3iq)$`+LZvRr@_UQ!U7q^{!#zEro ziOy&5I(40|&M=m4aly0y?CrVlkw^aZ^SkDY%$75z8fS0(!s~a5MH5|5{N!s}jjSzx zHMP@Htk%j>Lh+b!bmm7T|*we2td zv_|gc^B0Q2u}7aiVgvEPcb*f42VeZ}4%p;xFa6p{@2>I0c6}$G^RH9a=v&vg;*x8M z<2#Fki=D@xz0I$W>=MgYnYDuw>F-Rxb5++9+aEV;t&*yXK{io5PVrpI{m$&(+rT}%^;jyz&Y>iMtE6mx&G_yuvs7}d_$`m+hK;D7gi zQy32{IZNC(@8dI4*WmJT?S-%RzIe+s7u}th_TnuMUIYXSKKAr~U&@JfPy4{D9aC2R z`-U!K31E;b*Z zb?C9vL}BpM9lt&M@aK=&Wd0u?%wP8E(Mx&|)5afsBYVrzEoN={?!c6za*w7`%P+X> z_Sx?~w(Qwsk9gk~@|}PEXZQRYmi=a|&-mgcn_lw9odSzq{@TCH&pUg{0m3-) z>cyEG54q}|b6;O?m&|D$h0%kb*!oY)&RcK(p0g4w4%&LHR}cT#nxC1y<>7PRW88nq z0Uuj)&$}`&&VKXqPo+My_UymEaLj9eeE#w$_f4%7sjIfy_x#6yciEzKj2kZL>U#X5 zJC>ZV=#8G6It!WGdpAt|_<|*;t$D%W^EO*w^bCJ`{-%!~GVSqB@$CLPbbWlyh3oD8 z`A;7^1s#v=w&=|FUezO>{p`0=r#*6hk4P<@y5!Xl9(Vu8ADZBPR&u;hIWk-Mg#qRsh?H5Z#@`!`J_3DhJ>j>alI`y)X z);WIq1M3)P{`i-P!e0lTU+>#*z4ig|$euHQ-zA)PVvEzUUp%iD=-|9bEmC%$uB-<65f&QBef_*;i@jJWhW zKcAm_Sft-CW*__d%8l=v_di#Wa&#nn$L8XJLpPcxmfyK_i-#Zm>WMJ3*@erei6>q= z=#Adf=8JXbi%TzicXRRFeoud3#^R$NKWyqNGmM8$d29AH3savuaG9}Sj@WzUmyS=0 zr~j7ixb(nBbNAfvAl7~$d*+J|tZ(eGZO>l6-D~Hhc;6rXa@~&apSJkf19rZ(;*3swh?Cxo+7rm@u&Bkkrl~L9e#k=fBqUj*>c(*H(oCK#x}^^mF&41SCq33 zz4U@)&vzf|$SvAr+8&GlAo|vR@a3&~*E4oG{2;OasXHEb{4-yBX6mC)&w6RDcw|Ax zv4{6>b?py_|IquH?dOOqJ5n=e-8RK|ZQsN8J^EMqLxvAt3%cL0{1YyYkDUML^c1$~ zXHQ)F;V->P64qm?5Ay2Py@UNGi|M+doUf5&v zlk=U%soj@7CoWwKB0u!0M;2Xu{$YFl?XcH-#KU{Or{jm;e`l7s^~{g%^Y#A3<2%mY z;}<&}Hbk03zOw9{CF0*V7WO=Hboja*@B8lU^DqC?p<=IBW_;t3YrE1{U-_Zp`l&}> znbFg6)XHbh7B4=1-67}gGd(F5&!09pnmXg)sf%Cu#7$lA5l{U42MZ6)KbPC~r!$gM zo_OtNS3P#b#Db~oLI3UCZDU_6tZ`-HrMnlrdX)CZ>Q_J!Lw+$aBB>cjsy=d?Ws z6Up~pYlKAi4#sYPVQKB_{DRQ2j;hb_u@il>e-(RO#AfuKY8t&-$;LD>B@b+JH`0f z^o8Be&iUshH$Qyk1HWE){)zVweiC>t-uS{DuYa|Bm#fb>Y~+!emR(+0Vh za^wH``4=zVN-REMmwT^zY~F=m+3m;&F1+F37azN3hj&k0_ZacyBinEC=&ry1^dWoi z-gnsPBcl^vUH4S6*VfmZzfbmqFKzILo@?Kk)%A}ZU;N zEPVWx`78f)*)&r(``#xX8(+BfgZKR77jKSix}Nyh=dL-o=e{ZXUA6m%-t3$>`>;DqIy)lY`*jN zC-(o$=EjW=owEH7JG}3@i>4m<=7UFU_F!`SJ>us}w>W9`zUS;Rw0PF3znWLvYD(^X zADOyv6xqCViw`Y1YpcB{=52Z3NhdE|-ZLflgGbg{zW@HupFaE7uOStd>~zKlublGz zxi_Ei)*<;D{=Hzm!6UKHa3y;8=6^3*_VgQrKYIB7jSj^dq(40L&+pwNKEG_)2R=9d zJ>Opcvpa3P+ZvC5@vfu3`rb2-`D5bPgWkPy(GfGY`RIc4?pQK!*YQoia$I6yp9Ra0 z`PP^B&Y$?Pm3OWA`a?aNe{TJ2Z<+p$M_x{EKkZ{;@doq%_^YwKuD$w}d;h)lg0+)> z-eC7vK7IYmKkZoS{7X9*J#~Kas$1@Tc668F^TpsMi~BCzM4YhZ)CaFyJhsWFCqBR3 z))yRb)dv4P_=%ere|P4{nO~T0T=VvI`yTMk*T3FJYxGN7Y_b)#b-YG<#S&dy5q3xzVrJPe}6KY zS-Je{zxn9PdyoF&qy5{hdV8Dy%|BC2?7rgYi#jiuvn}0{?EQ)T_PBkw^m+duBh`VC literal 0 HcmV?d00001 diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomAction.config b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomAction.config new file mode 100644 index 0000000..c837a2c --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomAction.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomActions.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomActions.cs new file mode 100644 index 0000000..1c4795a --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/CustomActions.cs @@ -0,0 +1,142 @@ +namespace Microsoft.InnerEye.Listener.Wix.Actions +{ + using System; +#if DEBUG + using System.Diagnostics; +#endif + using System.IO; + using System.Net.Http; + using System.Threading.Tasks; + using System.Windows.Forms; + + using Microsoft.Deployment.WindowsInstaller; + using Microsoft.InnerEye.Azure.Segmentation.Client; + using Microsoft.InnerEye.Gateway.Models; + using Microsoft.InnerEye.Listener.Common.Providers; + + /// + /// The collection of custom actions run by the WiX installer. + /// + public static class CustomActions + { + /// + /// The command line argument to silent install. + /// + private const string UILevelCustomActionKey = "UILevel"; + + /// + /// Gets the install path. + /// + /// + /// The install path. + /// + private static string InstallPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Microsoft InnerEye Gateway"); + + /// + /// Gets the processor settings path. + /// + /// + /// The processor settings path. + /// + private static string ProcessorInstallDirectory => Path.Combine(InstallPath, "Microsoft InnerEye Gateway Processor"); + + /// + /// The pre-install custom action. + /// Asks the user for a license-key and validates it before continuing with the install. + /// + /// The session. + /// The action result. + [CustomAction] + public static ActionResult ValidateProductKey(Session session) + { +#if DEBUG + Debugger.Launch(); +#endif + + // Check if the installer is running unattended - lets skip the UI if true + if (session.CustomActionData[UILevelCustomActionKey] == "2") + { + return ActionResult.Success; + } + + var gatewayProcessorConfigProvider = new GatewayProcessorConfigProvider( + null, + ProcessorInstallDirectory); + + var processorSettings = gatewayProcessorConfigProvider.ProcessorSettings(); + + // First time install so lets display a form to grab the license key. + DialogResult licenseKeyDialogResult = DialogResult.No; + // TODO FIX INSTALLER + using (var form = new LicenseKeyForm(processorSettings)) + { + licenseKeyDialogResult = form.ShowDialog(); + } + + switch (licenseKeyDialogResult) + { + case DialogResult.Cancel: + return ActionResult.UserExit; + case DialogResult.No: + return ActionResult.NotExecuted; + default: + return ActionResult.Success; + } + } + + /// + /// The pre-uninstall custom action. + /// + /// The WiX session. + /// The action result. + [CustomAction] + public static ActionResult PreUninstall(Session session) + { + // Note: this method runs with Admin privileges. To debug Visual Studio must be running as Admin. +#if DEBUG + Debugger.Launch(); +#endif + + return ActionResult.Success; + } + + /// + /// Validates the license key using the InnerEye segmentation client. + /// + /// Processor settings. + /// The license key to validate. + /// If valid and text to display with the validation result. + internal static async Task<(bool Result, string ValidationText)> ValidateLicenseKeyAsync(ProcessorSettings processorSettings, string licenseKey) + { + var validationText = string.Empty; + var existingLicenseKey = Environment.GetEnvironmentVariable(processorSettings.LicenseKeyEnvVar); + + try + { + // Update the settings for the Gateway. + Environment.SetEnvironmentVariable(processorSettings.LicenseKeyEnvVar, licenseKey); + + using (var segmentationClient = new InnerEyeSegmentationClient(processorSettings.InferenceUri, processorSettings.LicenseKeyEnvVar)) + { + await segmentationClient.PingAsync(); + } + + return (true, validationText); + } + catch (HttpRequestException) + { + validationText = "Failed to connect to the internet"; + // Restore the previous environment variable + Environment.SetEnvironmentVariable(processorSettings.LicenseKeyEnvVar, existingLicenseKey); + } + catch (Exception) + { + validationText = "Invalid product key"; + // Restore the previous environment variable + Environment.SetEnvironmentVariable(processorSettings.LicenseKeyEnvVar, existingLicenseKey); + } + + return (false, validationText); + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.Designer.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.Designer.cs new file mode 100644 index 0000000..79d79de --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.Designer.cs @@ -0,0 +1,195 @@ +namespace Microsoft.InnerEye.Listener.Wix.Actions +{ + partial class LicenseKeyForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(LicenseKeyForm)); + this.MainCancelButton = new System.Windows.Forms.Button(); + this.NextButton = new System.Windows.Forms.Button(); + this.BackButton = new System.Windows.Forms.Button(); + this.titlePanel = new System.Windows.Forms.Panel(); + this.panel2 = new System.Windows.Forms.Panel(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + this.panel1 = new System.Windows.Forms.Panel(); + this.licenseKeyTextBox = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.invalidKeyLabel = new System.Windows.Forms.Label(); + this.titlePanel.SuspendLayout(); + this.SuspendLayout(); + // + // MainCancelButton + // + this.MainCancelButton.Cursor = System.Windows.Forms.Cursors.Default; + this.MainCancelButton.Location = new System.Drawing.Point(407, 311); + this.MainCancelButton.Name = "MainCancelButton"; + this.MainCancelButton.Size = new System.Drawing.Size(75, 23); + this.MainCancelButton.TabIndex = 0; + this.MainCancelButton.Text = "Cancel"; + this.MainCancelButton.UseVisualStyleBackColor = true; + this.MainCancelButton.Click += new System.EventHandler(this.CancelButton_Click); + // + // NextButton + // + this.NextButton.Location = new System.Drawing.Point(310, 311); + this.NextButton.Name = "NextButton"; + this.NextButton.Size = new System.Drawing.Size(75, 23); + this.NextButton.TabIndex = 1; + this.NextButton.Text = "Next"; + this.NextButton.UseVisualStyleBackColor = true; + this.NextButton.Click += new System.EventHandler(this.NextButton_Click); + // + // BackButton + // + this.BackButton.Enabled = false; + this.BackButton.Location = new System.Drawing.Point(229, 311); + this.BackButton.Name = "BackButton"; + this.BackButton.Size = new System.Drawing.Size(75, 23); + this.BackButton.TabIndex = 2; + this.BackButton.Text = "Back"; + this.BackButton.UseVisualStyleBackColor = true; + this.BackButton.Click += new System.EventHandler(this.BackButton_Click); + // + // titlePanel + // + this.titlePanel.BackColor = System.Drawing.SystemColors.ControlLightLight; + this.titlePanel.Controls.Add(this.panel2); + this.titlePanel.Controls.Add(this.label2); + this.titlePanel.Controls.Add(this.label1); + this.titlePanel.Location = new System.Drawing.Point(-5, -4); + this.titlePanel.Name = "titlePanel"; + this.titlePanel.Size = new System.Drawing.Size(506, 73); + this.titlePanel.TabIndex = 3; + // + // panel2 + // + this.panel2.BackColor = System.Drawing.SystemColors.ControlDarkDark; + this.panel2.ForeColor = System.Drawing.SystemColors.ControlLightLight; + this.panel2.Location = new System.Drawing.Point(3, 72); + this.panel2.Name = "panel2"; + this.panel2.Size = new System.Drawing.Size(502, 1); + this.panel2.TabIndex = 2; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label2.Location = new System.Drawing.Point(32, 40); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(191, 13); + this.label2.TabIndex = 1; + this.label2.Text = "Click Next to validate the product key."; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Font = new System.Drawing.Font("Tahoma", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label1.Location = new System.Drawing.Point(18, 17); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(82, 14); + this.label1.TabIndex = 0; + this.label1.Text = "Product Key"; + // + // panel1 + // + this.panel1.BackColor = System.Drawing.SystemColors.ControlDarkDark; + this.panel1.Location = new System.Drawing.Point(-5, 300); + this.panel1.Name = "panel1"; + this.panel1.Size = new System.Drawing.Size(507, 1); + this.panel1.TabIndex = 4; + // + // licenseKeyTextBox + // + this.licenseKeyTextBox.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.licenseKeyTextBox.Location = new System.Drawing.Point(16, 152); + this.licenseKeyTextBox.Name = "licenseKeyTextBox"; + this.licenseKeyTextBox.Size = new System.Drawing.Size(466, 21); + this.licenseKeyTextBox.TabIndex = 5; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Font = new System.Drawing.Font("Tahoma", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.label3.Location = new System.Drawing.Point(16, 93); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(153, 13); + this.label3.TabIndex = 6; + this.label3.Text = "Use the following product key:"; + // + // invalidKeyLabel + // + this.invalidKeyLabel.AutoSize = true; + this.invalidKeyLabel.ForeColor = System.Drawing.Color.Red; + this.invalidKeyLabel.Location = new System.Drawing.Point(385, 187); + this.invalidKeyLabel.Name = "invalidKeyLabel"; + this.invalidKeyLabel.Size = new System.Drawing.Size(97, 13); + this.invalidKeyLabel.TabIndex = 7; + this.invalidKeyLabel.Text = "Invalid product key"; + this.invalidKeyLabel.Visible = false; + // + // LicenseKeyForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(494, 346); + this.Controls.Add(this.invalidKeyLabel); + this.Controls.Add(this.label3); + this.Controls.Add(this.licenseKeyTextBox); + this.Controls.Add(this.panel1); + this.Controls.Add(this.titlePanel); + this.Controls.Add(this.BackButton); + this.Controls.Add(this.NextButton); + this.Controls.Add(this.MainCancelButton); + this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); + this.Name = "LicenseKeyForm"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "Microsoft InnerEye Gateway Setup"; + this.TopMost = true; + this.Load += new System.EventHandler(this.LicenseKeyForm_Load); + this.titlePanel.ResumeLayout(false); + this.titlePanel.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button MainCancelButton; + private System.Windows.Forms.Button NextButton; + private System.Windows.Forms.Button BackButton; + private System.Windows.Forms.Panel titlePanel; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Panel panel1; + private System.Windows.Forms.TextBox licenseKeyTextBox; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Label invalidKeyLabel; + private System.Windows.Forms.Panel panel2; + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.cs new file mode 100644 index 0000000..3fb73d9 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.cs @@ -0,0 +1,93 @@ +namespace Microsoft.InnerEye.Listener.Wix.Actions +{ + using System; + using System.Threading.Tasks; + using System.Windows.Forms; + + using Microsoft.InnerEye.Gateway.Models; + + public partial class LicenseKeyForm : Form + { + /// + /// The temporary settings. + /// + private readonly ProcessorSettings _processorSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The temporary settings. + public LicenseKeyForm(ProcessorSettings processorSettings) + { + _processorSettings = processorSettings; + + InitializeComponent(); + + Application.EnableVisualStyles(); + } + + /// + /// Handles the Load event of the LicenseKeyForm control. + /// + /// The source of the event. + /// The instance containing the event data. + private void LicenseKeyForm_Load(object sender, EventArgs e) + { + licenseKeyTextBox.Text = Environment.GetEnvironmentVariable(_processorSettings.LicenseKeyEnvVar) ?? string.Empty; + } + + /// + /// Handles the Click event of the NextButton control. + /// + /// The source of the event. + /// The instance containing the event data. + private async void NextButton_Click(object sender, EventArgs e) + { + NextButton.Enabled = false; + + await ValidateLicenseKeyAsync(); + + NextButton.Enabled = true; + } + + /// + /// Validates the license key using the segmentation client and sets the dialog result. + /// + /// + private async Task ValidateLicenseKeyAsync() + { + var licenseKey = licenseKeyTextBox.Text; + var (result, validationText) = await CustomActions.ValidateLicenseKeyAsync(_processorSettings, licenseKey); + + invalidKeyLabel.Text = validationText; + + if (!result) + { + invalidKeyLabel.Visible = true; + return; + } + + DialogResult = DialogResult.Yes; + } + + /// + /// Handles the Click event of the CancelButton control. + /// + /// The source of the event. + /// The instance containing the event data. + private void CancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + } + + /// + /// Handles the Click event of the BackButton control. + /// + /// The source of the event. + /// The instance containing the event data. + private void BackButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.No; + } + } +} \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.resx b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.resx new file mode 100644 index 0000000..6db079f --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/LicenseKeyForm.resx @@ -0,0 +1,6257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + AAABABUAEBAAAAEAIABoBAAAVgEAABQUAAABACAAuAYAAL4FAAAYGAAAAQAgAIgJAAB2DAAAHBwAAAEA + IADYDAAA/hUAAB4eAAABACAAsA4AANYiAAAfHwAAAQAgAKgPAACGMQAAICAAAAEAIACoEAAALkEAACgo + AAABACAAaBoAANZRAAAqKgAAAQAgAAgdAAA+bAAALy8AAAEAIAAkJAAARokAADAwAAABACAAqCUAAGqt + AAA4OAAAAQAgAOgyAAAS0wAAPDwAAAEAIABIOgAA+gUBAD8/AAABACAAJEAAAEJAAQBAQAAAAQAgAChC + AABmgAEARkYAAAEAIAAAUAAAjsIBAFRUAAABACAAWHIAAI4SAgBgYAAAAQAgAKiUAADmhAIAgIAAAAEA + IAAoCAEAjhkDAJaWAAABACAAcGsBALYhBAAAAAAAAQAgANEPAAAmjQUAKAAAABAAAAAgAAAAAQAgAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInn3KCJ+9ygif/coInEAAA + AABygiePcoIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/wAA + AABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygicgcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicgcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+8AAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiefAAAAAHKCJyBygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInIAAAAAAAAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIncAAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygiefcoIn73KCJ/8AAAAAcoIn/3KC + J+9ygiefcoInIAAAAAAAAAAAAAAAAAAAAAD//wAA4IMAAMEBAACBAQAAAQAAAAEBAAABAQAAAQMAAP// + AAABAQAAAQEAAAEBAAABAQAAgQMAAMEHAADhDwAAKAAAABQAAAAoAAAAAQAgAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoInr3KC + J/9ygif/coInEAAAAABygicgcoInr3KCJ/9ygif/coIn/3KCJ69ygicgAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn33KCJ/9ygif/coIn/3KCJ/8AAAAAcoInIHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicgAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygievcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ68AAAAAAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInrwAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicgAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygievcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInrwAAAABygidgcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidgAAAAAAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicwAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygiffcoIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn33KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoInr3KC + J/9ygif/AAAAAHKCJ/9ygif/coInr3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///AA+CAwAOBA + EADAQBAAgEAQAIBAEAAAQBAAAEAQAABAEAAAQDAA///wAABAEAAAQBAAAEAQAABAEACAQDAAgEAwAMBA + cADgQPAA+EPwACgAAAAYAAAAMAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIncHKC + J89ygif/coIn/3KCJxAAAAAAAAAAAHKCJ0Bygie/coIn/3KCJ/9ygif/coInv3KCJ0AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ49ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoInz3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInQAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAcoInEHKC + J89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygidwcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInvwAAAABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInr3KCJ0AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInzwAAAABygidwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAA + AABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAAAAAAAAAAAAAAAHKC + JzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInMAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ49ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ49ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicgcoIncHKCJ89ygif/coIn/wAAAABygif/coIn/3KCJ89ygidwcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAD///8A/AwHAPAYAwDgEAEAwBABAIAQAQCAEAEAABABAAAQAQAAEAEAABADAAAQ + BwD///8AABABAAAQAQAAEAEAABABAAAQAQCAEAMAgBADAMAQBwDgEA8A8BAfAPwQfwAoAAAAHAAAADgA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J49ygifPcoIn/3KCJ/9ygicQAAAAAAAAAAAAAAAAcoIngHKCJ99ygif/coIn/3KCJ/9ygiffcoIncAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygicQcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAAAAAAAAAAAAAAAHKC + JxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAABygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJyAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiffAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIngAAA + AABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJxAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifPcoInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J88AAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAHKC + JzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAAAAAAAAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J89ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ49ygicQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoInj3KCJ89ygif/coIn/wAAAABygif/coIn/3KC + J89ygiePcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//9/D/A4Dw/AYAMPAG + ADDgBAAQwAQAEMAEABCABAAAgAQAEAAEABAABAAQAAQAMAAEADAABADw////8AAEABAABAAQAAQAEAAE + ABAABAAQgAQAMIAEADDABABwwAQAcOAEAPDwBAHw/AQH8P8EH/AoAAAAHgAAADwAAAABACAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKC + J59ygifPcoIn/3KCJ/9ygicQAAAAAAAAAAAAAAAAcoInQHKCJ69ygif/coIn/3KCJ/9ygif/coInr3KC + J0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2BygiffcoIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0BygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInMHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ68AAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygicQAAAAAHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ68AAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ0AAAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInjwAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygieAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInn3KC + JzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ88AAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ58AAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ1AAAAAAAAAAAHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn3wAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAAAAAAAAAAABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifPAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0BygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInQAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicwAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2BygiffcoIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKC + J59ygifPcoIn/3KCJ/8AAAAAcoIn/3KCJ/9ygifPcoInn3KCJ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA/////P+BwDz+A4Ac+AMADPACAATgAgAEwAIABMACAASAAgAAgAIABAAC + AAQAAgAEAAIADAACABwAAgA8/////AACAAQAAgAEAAIABAACAAQAAgAEgAIADIACAAzAAgAcwAIAHOAC + ADzwAgB8+AIA/P4CA/z/gg/8KAAAAB8AAAA+AAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygidgcoInr3KCJ99ygif/coIn/wAA + AAAAAAAAAAAAAAAAAABygicQcoInj3KCJ+9ygif/coIn/3KCJ/9ygifvcoInn3KCJyAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygicQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJyAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoInEHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJxBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygidgcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePcoInr3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInQAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygidwcoInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ99ygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygievcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInYHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJxAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicQAAAAAAAAAAAAAAAAcoInUHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygieAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJ4BygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygidgcoInr3KCJ99ygif/coIn/wAA + AABygif/coIn/3KCJ99ygievcoInYHKCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD/geAO/gHABvwBgAL4AQAA8AEAAOABAADAAQAAgAEAAIABAAAAAQAAAAEAAAABAAAAAQACAAEABgAB + AA7////+AAEAAAABAAAAAQAAAAEAAAABAAAAAQAAgAEAAoABAALAAQAG4AEADvABAB74AQA+/AEAfv4B + AP7/gQP+KAAAACAAAABAAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ2BygievcoIn33KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJxBygiePcoIn73KCJ/9ygif/coIn/3KCJ+9ygiefcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1BygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygifPcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAABygidQcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJxAAAAAAcoInEHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAA + AABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygicQcoInEHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn3wAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiePAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJxAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInQAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoIncHKCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInIHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAcoInEHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEAAAAAAAAAAAcoIngHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAAAA + AABygicQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEAAA + AAAAAAAAAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoIngHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygieAcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ2BygievcoIn33KCJ/9ygif/AAAAAHKC + J/9ygif/coIn33KCJ69ygidgcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAD//f5//4HgD/4BwAf8AYAD+AEAAfABAAHgAQABwAEAAIABAACAAQAAAAEAAQABAAEAAQABAAEAAwAB + AAcAAQAP/////wABAAAAAQABAAEAAQABAAEAAQABAAEAAYABAAOAAQADwAEAB+ABAA/wAQAf+AEAP/wB + AH/+AQD//4ED/ygAAAAoAAAAUAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygieAcoInv3KCJ+9ygif/coIn/3KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0BygievcoIn73KCJ/9ygif/coIn/3KCJ+9ygievcoInQAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInIHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAcoInMHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInEHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + JyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievAAAAAAAAAAAAAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn7wAAAAAAAAAAAAAAAAAA + AABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInIAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+8AAAAAAAAAAHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAABygieAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + JyAAAAAAAAAAAHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJzAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInr3KCJxAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygiefcoInMAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn7wAAAABygie/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygieAAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInMAAAAAAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J2AAAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInMHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInMAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInYHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KC + J2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ4Bygie/coIn73KC + J/9ygif/AAAAAHKCJ/9ygif/coIn73KCJ79ygieAcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////+/8AAAD/+A+APwAAAP/gHgAPAAAA/4AcAAcA + AAD+ABgAAwAAAPwAGAADAAAA+AAQAAEAAADwABAAAQAAAOAAEAABAAAA4AAQAAEAAADAABAAAAAAAMAA + EAABAAAAgAAQAAEAAACAABAAAQAAAAAAEAABAAAAAAAQAAMAAAAAABAAAwAAAAAAEAAHAAAAAAAQAA8A + AAAAABAAPwAAAP//////AAAAAAAQAAEAAAAAABAAAQAAAAAAEAABAAAAAAAQAAEAAAAAABAAAQAAAAAA + EAABAAAAgAAQAAMAAACAABAAAwAAAMAAEAAHAAAAwAAQAAcAAADgABAADwAAAOAAEAAPAAAA8AAQAB8A + AAD4ABAAPwAAAPwAEAB/AAAA/gAQAP8AAAD/gBAD/wAAAP/gEA//AAAA//gQP/8AAAAoAAAAKgAAAFQA + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIngHKCJ79ygifvcoIn/3KCJ/9ygicQAAAAAAAAAAAAAAAAAAAAAAAA + AABygicgcoInj3KCJ99ygif/coIn/3KCJ/9ygif/coInz3KCJ49ygicgAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoIngHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygicQcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoInEHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + JyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAcoInQHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygicQcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygicQAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygicQAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAcoInMHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJyAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIngAAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPAAAAAAAAAAAAAAAAcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygicQAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoIngAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJ3BygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+8AAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ78AAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J4AAAAAAcoInMHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJzAAAAAAAAAAAHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIngAAAAAAAAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygieAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygicQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ3Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J99ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoIngHKCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoIngHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn33KCJ4BygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIngHKCJ79ygifvcoIn/3KCJ/8AAAAAcoIn/3KCJ/9ygifvcoInv3KC + J4BygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAA///////AAAD//AfAD8AAAP/gD4AHwAAA/4AOAAHAAAD/AA4AAcAAAP4ADAAAwAAA/AAIAABA + AAD4AAgAAEAAAPAACAAAQAAA4AAIAABAAADAAAgAAAAAAMAACAAAAAAAgAAIAABAAACAAAgAAEAAAIAA + CAAAQAAAAAAIAABAAAAAAAgAAMAAAAAACAABwAAAAAAIAAHAAAAAAAgAB8AAAAAACAAPwAAA///////A + AAAAAAgAAEAAAAAACAAAQAAAAAAIAABAAAAAAAgAAEAAAAAACAAAQAAAAAAIAABAAACAAAgAAMAAAIAA + CAAAwAAAgAAIAADAAADAAAgAAcAAAMAACAABwAAA4AAIAAPAAADwAAgAB8AAAPgACAAPwAAA/AAIAB/A + AAD+AAgAP8AAAP8ACAB/wAAA/4AIAP/AAAD/4AgD/8AAAP/8CB//wAAAKAAAAC8AAABeAAAAAQAgAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1BygiePcoInv3KCJ+9ygif/coIn/3KC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ59ygiffcoIn/3KCJ/9ygif/coIn/3KC + J99ygiefcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ59ygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ1AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAcoInEHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + JyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInIHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygidwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAcoInEHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInEAAA + AAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygicQAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInMAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInIAAAAAAAAAAAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJzAAAAAAAAAAAAAA + AABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicwAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygiePcoInIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+8AAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInvwAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiePAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ1AAAAAAAAAAAHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAABygidAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAAAAAAAAAAAAAAAA + AABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiffcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J0Bygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidAcoInn3KCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygiefcoInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1BygiePcoInv3KCJ+9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygifvcoInv3KCJ49ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///////4AAP//AfwA/gAA//gD8AA+ + AAD/4APgAB4AAP+AA8AADgAA/wADgAAGAAD+AAOAAAYAAPwAAwAAAgAA+AADAAACAADwAAMAAAIAAOAA + AwAAAgAA4AADAAAAAADAAAMAAAAAAMAAAwAAAgAAgAADAAACAACAAAMAAAIAAIAAAwAAAgAAAAADAAAG + AAAAAAMAAAYAAAAAAwAADgAAAAADAAAeAAAAAAMAAD4AAAAAAwAA/gAA///////+AAD///////4AAAAA + AwAAAgAAAAADAAACAAAAAAMAAAIAAAAAAwAAAgAAAAADAAACAAAAAAMAAAIAAIAAAwAABgAAgAADAAAG + AACAAAMAAAYAAMAAAwAADgAAwAADAAAOAADgAAMAAB4AAOAAAwAAHgAA8AADAAA+AAD4AAMAAH4AAPwA + AwAA/gAA/gADAAH+AAD/AAMAA/4AAP+AAwAH/gAA/+ADAB/+AAD/+AMAf/4AAP//AwP//gAAKAAAADAA + AABgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInUHKCJ49ygie/coIn73KC + J/9ygif/coInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInYHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ79ygidwcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ69ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2BygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2BygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygieAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAHKCJzBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAHKC + J79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAABygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncHKCJxBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEHKC + J1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygieAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ88AAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInz3KCJxAAAAAAAAAAAHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInEAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ69ygidgAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInj3KCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUHKCJxBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInEAAA + AABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInMAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ3Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygicwAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2BygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ69ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInr3KC + J1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInUHKCJ49ygie/coIn73KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ+9ygie/coInj3KC + J1BygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAP//AP4APwAA//wB/AAfAAD/8AH4AAcAAP/AAfAAAwAA/4AB4AADAAD/AAHAAAEAAPwA + AcAAAAAA/AABgAAAAAD4AAGAAAAAAPAAAYAAAAAA4AABgAAAAADgAAGAAAAAAMAAAYAAAAAAwAABgAAA + AACAAAGAAAAAAIAAAYAAAAAAAAABgAAAAAAAAAGAAAEAAAAAAYAAAwAAAAABgAADAAAAAAGAAAcAAAAA + AYAAHwAAAAABgAB/AAD///////8AAP///////wAAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABgAAA + AAAAAAGAAAAAAAAAAYAAAAAAAAABgAAAAACAAAGAAAEAAIAAAYAAAQAAwAABgAADAADAAAGAAAMAAOAA + AYAABwAA4AABgAAHAADwAAGAAA8AAPgAAYAAHwAA/AABgAA/AAD8AAGAAD8AAP8AAYAA/wAA/4ABgAH/ + AAD/wAGAA/8AAP/wAYAP/wAA//wBgD//AAD//wGA//8AACgAAAA4AAAAcAAAAAEAIAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygidwcoInn3KCJ79ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ49ygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygifPcoInj3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInIHKCJ49ygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ79ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifPcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygicgcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J78AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJzAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ3Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifPAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAHKC + JzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPAAAAAHKCJyBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInMAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAcoInMHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ48AAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ59ygicgAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInv3KCJ3BygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInn3KCJ3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidwcoInMHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJzAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J48AAAAAAAAAAHKCJyBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAHKC + J79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInQAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1BygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifPcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInMAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInj3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInv3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInQHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJ0AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JyBygiePcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffcoInj3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J3BygiefcoInv3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coInv3KCJ59ygidwcoInMAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA///wH/gA/wD//4Af4AA/AP/+AB/AAB8A//gAH4AABwD/4AAfAAAHAP/A + AB4AAAMA/4AAHAAAAQD/AAAcAAABAP4AABgAAAAA/AAAGAAAAAD4AAAYAAAAAPAAABgAAAAA8AAAGAAA + AADgAAAYAAAAAOAAABgAAAAAwAAAGAAAAADAAAAYAAAAAIAAABgAAAAAgAAAGAAAAACAAAAYAAABAAAA + ABgAAAEAAAAAGAAAAwAAAAAYAAAHAAAAABgAAA8AAAAAGAAAHwAAAAAYAAA/AAAAABgAAP8A//////// + /wD/////////AAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAA + ABgAAAAAgAAAGAAAAQCAAAAYAAABAIAAABgAAAEAwAAAGAAAAwDAAAAYAAADAOAAABgAAAcA4AAAGAAA + BwDwAAAYAAAPAPAAABgAAA8A+AAAGAAAHwD8AAAYAAA/AP4AABgAAH8A/wAAGAAA/wD/gAAYAAH/AP/A + ABgAA/8A/+AAGAAH/wD/+AAYAB//AP/+ABgAf/8A//+AGAH//wD///AYD///ACgAAAA8AAAAeAAAAAEA + IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J3BygievcoInv3KCJ/9ygif/coIn/3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInUHKCJ49ygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInj3KCJ0AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ59ygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2BygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ4BygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J79ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAABygicQcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInIHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygie/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInMHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ1AAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInYAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygie/AAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicQAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJzAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInMAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J69ygicQAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInQAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coIngHKCJzAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ69ygidwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ3BygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJzAAAAAAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn7wAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInUAAAAAAAAAAAAAAAAHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygieAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J89ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + JyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInMAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicwAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJzAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInIHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ4BygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ99ygieAcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ59ygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygiefcoInUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J3BygievcoInv3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coInv3KCJ69ygidwcoInMAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///wD/wAf8P//4Af8AAfw//8AB/AAAfD//gAH4AAA8P/4 + AAfAAABw/+AAB4AAADD/wAAHgAAAMP+AAAcAAAAQ/wAABwAAABD+AAAGAAAAAPwAAAYAAAAA/AAABgAA + AAD4AAAGAAAAAPAAAAYAAAAA8AAABgAAAADgAAAGAAAAAMAAAAYAAAAAwAAABgAAAADAAAAGAAAAAIAA + AAYAAAAAgAAABgAAABCAAAAGAAAAEAAAAAYAAAAwAAAABgAAADAAAAAGAAAAcAAAAAYAAADwAAAABgAA + AfAAAAAGAAAH8AAAAAYAAB/w//////////D/////////8AAAAAYAAAAAAAAABgAAAAAAAAAGAAAAAAAA + AAYAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAgAAABgAAABCAAAAGAAAAEIAAAAYAAAAQwAAABgAA + ADDAAAAGAAAAMMAAAAYAAAAw4AAABgAAAHDwAAAGAAAA8PAAAAYAAADw+AAABgAAAfD4AAAGAAAB8PwA + AAYAAAPw/gAABgAAB/D/AAAGAAAP8P+AAAYAAB/w/8AABgAAP/D/8AAGAAD/8P/4AAYAAf/w//4ABgAH + //D//wAGAA//8P//4AYAf//w///8BgP///AoAAAAPwAAAH4AAAABACAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIngHKC + J69ygifPcoIn/3KCJ/9ygif/coInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicgcoIncHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygie/coIncHKCJyAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoInr3KC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInj3KC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidgcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInIHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInMAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInrwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ3Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicgcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInEHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygicgcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+8AAAAAAAAAAAAA + AABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J78AAAAAAAAAAAAAAABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ3AAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJyAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEAAAAAAAAAAAcoInQHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J58AAAAAAAAAAAAAAAAAAAAAcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInnwAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygieAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn33KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygieAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygiefcoInUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ68AAAAAcoIngHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAcoInQHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J0AAAAAAAAAAAHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn7wAAAAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInrwAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAAAABygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvAAAAAAAAAAAAAAAAAAAAAAAA + AABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAA + AAAAAAAAAAAAAAAAAABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicgAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ3Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJyAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidwAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInn3KC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicgcoInj3KCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + J49ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoInr3KCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + J69ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidAcoIngHKCJ69ygifPcoIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygifPcoInr3KC + J4BygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//////////7///4B/4AH/v// + 8AP+AAH+//+AA/wAAP7//gAD+AAAPv/8AAPwAAAe//AAA+AAAB7/4AADwAAADv/AAAPAAAAG/4AAA4AA + AAb/AAADgAAAAv4AAAMAAAAC/AAAAwAAAAL4AAADAAAAAvgAAAMAAAAC8AAAAwAAAALgAAADAAAAAuAA + AAMAAAACwAAAAwAAAALAAAADAAAAAsAAAAMAAAACgAAAAwAAAAKAAAADAAAABoAAAAMAAAAGAAAAAwAA + AA4AAAADAAAAHgAAAAMAAAA+AAAAAwAAAH4AAAADAAAA/gAAAAMAAAP+AAAAAwAAD/7//////////v// + ///////+AAAAAwAAAAIAAAADAAAAAgAAAAMAAAACAAAAAwAAAAIAAAADAAAAAgAAAAMAAAACAAAAAwAA + AAKAAAADAAAABoAAAAMAAAAGgAAAAwAAAAbAAAADAAAADsAAAAMAAAAOwAAAAwAAAA7gAAADAAAAHuAA + AAMAAAAe8AAAAwAAAD74AAADAAAAfvgAAAMAAAB+/AAAAwAAAP7+AAADAAAB/v8AAAMAAAP+/4AAAwAA + B/7/wAADAAAP/v/gAAMAAB/+//AAAwAAP/7//AADAAD//v/+AAMAAf/+//+AAwAH//7///ADAD///v// + /gMB///+KAAAAEAAAACAAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0BygieAcoInr3KCJ89ygif/coIn/3KCJ/9ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoInj3KCJ79ygif/coIn/3KC + J/9ygif/coIn73KCJ79ygiePcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygidgcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4BygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifPcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J1BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAcoInEHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicwAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInn3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvAAAAAAAAAAAAAAAAAAAAAHKCJxBygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoInIHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAHKC + J59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAHKCJxBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInUAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAcoInr3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ3AAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J98AAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygieAAAAAAAAAAAAAAAAAcoInr3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAHKC + J89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygieAAAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J99ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJ4BygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ89ygiefcoInUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievcoIngHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIngHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ69ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAAAA + AABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAcoInEHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJxAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAcoInMHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicwAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygie/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J89ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ79ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInUHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInj3KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J59ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInYHKCJ69ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievcoInYHKCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0BygieAcoInr3KCJ89ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J89ygievcoIngHKCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8A/+AD//// + 8AH/gAD////AAf4AAD///wAB/AAAH//8AAH4AAAP//gAAfAAAAf/8AAB4AAAA//AAAHAAAAD/4AAAcAA + AAH/AAABwAAAAf4AAAGAAAAA/gAAAYAAAAD8AAABgAAAAPgAAAGAAAAA8AAAAYAAAADwAAABgAAAAOAA + AAGAAAAA4AAAAYAAAADAAAABgAAAAMAAAAGAAAAAgAAAAYAAAACAAAABgAAAAYAAAAGAAAABgAAAAYAA + AAMAAAABgAAAAwAAAAGAAAAHAAAAAYAAAA8AAAABgAAAHwAAAAGAAAA/AAAAAYAAAH8AAAABgAAD//// + //////////////////8AAAABgAAAAAAAAAGAAAAAAAAAAYAAAAAAAAABgAAAAAAAAAGAAAAAAAAAAYAA + AAAAAAABgAAAAIAAAAGAAAABgAAAAYAAAAGAAAABgAAAAYAAAAGAAAABwAAAAYAAAAPAAAABgAAAA+AA + AAGAAAAH4AAAAYAAAAfwAAABgAAAD/AAAAGAAAAP+AAAAYAAAB/8AAABgAAAP/4AAAGAAAB//gAAAYAA + AH//AAABgAAA//+AAAGAAAH//8AAAYAAA///8AABgAAP///4AAGAAB////wAAYAAP////wABgAD///// + wAGAA//////wAYAP//////8BgP///ygAAABGAAAAjAAAAAEAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIncHKC + J49ygie/coIn33KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygidgcoInn3KCJ79ygiffcoIn/3KCJ/9ygiffcoInv3KCJ49ygidQcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicgcoIncHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefcoInMAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1BygievcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAABygicQcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInj3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAcoInIHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJxBygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J78AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAAAAAAAAAAAAAAAA + AABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J58AAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAABygievcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInEAAAAABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAA + AAAAAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJyAAAAAAAAAAAHKCJ69ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygicQAAAAAAAA + AABygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicwAAAAAAAAAAAAAAAAcoIncHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidgAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidgAAAAAAAAAAAAAAAAAAAAAAAA + AABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ99ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ49ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInn3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJ59ygidgcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J99ygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInj3KCJ3Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J3BygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInrwAA + AAAAAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ3AAAAAAAAAAAHKCJyBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygicgAAAAAAAAAAAAAAAAcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAAAA + AAAAAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInz3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KC + JyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInn3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygie/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygidQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn33KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInUHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievcoInUAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKC + J3BygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInr3KCJ3BygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIncHKC + J59ygie/coIn33KCJ/9ygif/AAAAAAAAAABygif/coIn/3KCJ99ygie/coInn3KCJ3BygidAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////4D/+AB/8AAAA///+AD/4 + AAf8AAAA///4AD/gAAH8AAAA///gAD/AAAD8AAAA//+AAD+AAAB8AAAA//4AAD8AAAA8AAAA//wAAD4A + AAAcAAAA//AAADwAAAAMAAAA/+AAADwAAAAMAAAA/8AAADgAAAAEAAAA/4AAADgAAAAEAAAA/wAAADAA + AAAAAAAA/gAAADAAAAAAAAAA/gAAADAAAAAAAAAA/AAAADAAAAAAAAAA+AAAADAAAAAAAAAA+AAAADAA + AAAAAAAA8AAAADAAAAAAAAAA8AAAADAAAAAAAAAA4AAAADAAAAAAAAAA4AAAADAAAAAAAAAAwAAAADAA + AAAAAAAAwAAAADAAAAAAAAAAgAAAADAAAAAEAAAAgAAAADAAAAAEAAAAgAAAADAAAAAMAAAAgAAAADAA + AAAMAAAAAAAAADAAAAAcAAAAAAAAADAAAAA8AAAAAAAAADAAAAB8AAAAAAAAADAAAAD8AAAAAAAAADAA + AAH8AAAAAAAAADAAAAf8AAAAAAAAADAAAB/8AAAA///////////8AAAA///////////8AAAAAAAAADAA + AAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAAAAAAAAAAAAAAADAA + AAAAAAAAAAAAADAAAAAAAAAAgAAAADAAAAAEAAAAgAAAADAAAAAEAAAAgAAAADAAAAAEAAAAgAAAADAA + AAAEAAAAwAAAADAAAAAMAAAAwAAAADAAAAAMAAAA4AAAADAAAAAcAAAA4AAAADAAAAAcAAAA8AAAADAA + AAA8AAAA8AAAADAAAAA8AAAA+AAAADAAAAB8AAAA+AAAADAAAAB8AAAA/AAAADAAAAD8AAAA/gAAADAA + AAH8AAAA/gAAADAAAAH8AAAA/wAAADAAAAP8AAAA/4AAADAAAAf8AAAA/8AAADAAAA/8AAAA/+AAADAA + AB/8AAAA//AAADAAAD/8AAAA//wAADAAAP/8AAAA//4AADAAAf/8AAAA//+AADAAB//8AAAA///gADAA + H//8AAAA///4ADAAf//8AAAA///+ADAB///8AAAA////4DAf///8AAAAKAAAAFQAAACoAAAAAQAgAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0BygidgcoIngHKCJ69ygie/coInz3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ2BygiefcoInz3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygiefcoInYHKCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ4Bygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJyBygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygievcoInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J49ygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J79ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2BygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ1BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiffcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygie/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInEHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ68AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygieAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInEAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifPAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAA + AAAAAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAAAAAAAAcoInMHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygicQAAAAAAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJyAAAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAAAA + AABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KC + J4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygidwcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ79ygieAcoInMAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKC + J79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiffAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/AAAAAHKCJ2Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiePAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidwAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicwAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ68AAAAAAAAAAAAAAABygicwcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ3AAAAAAAAAAAAAAAAAAAAAAcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJyAAAAAAAAAAAAAA + AAAAAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicgAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJyAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicwAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ1AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInYHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygie/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygicwAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInUHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffcoInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidgcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ4BygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2BygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ99ygidgcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ49ygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygiefcoInMAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ4Bygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J79ygiePcoInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0BygidgcoIngHKCJ69ygie/coInz3KCJ/8AAAAAAAAAAAAA + AABygif/coInz3KCJ79ygie/coIngHKCJ2BygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////////wAP// + //+A//8AA//wAP////gA//wAAP/wAP///8AA//AAAD/wAP///wAA/+AAAB/wAP///AAA/8AAAAfwAP// + 8AAA/4AAAAPwAP//wAAA/wAAAAHwAP//gAAA/gAAAAHwAP/+AAAA/AAAAADwAP/8AAAA/AAAAABwAP/4 + AAAA+AAAAABwAP/wAAAA8AAAAAAwAP/gAAAA8AAAAAAwAP/AAAAA8AAAAAAQAP+AAAAA4AAAAAAQAP8A + AAAA4AAAAAAQAP8AAAAA4AAAAAAQAP4AAAAA4AAAAAAQAPwAAAAA4AAAAAAQAPwAAAAA4AAAAAAAAPgA + AAAA4AAAAAAAAPgAAAAA4AAAAAAQAPAAAAAA4AAAAAAQAPAAAAAA4AAAAAAQAOAAAAAA4AAAAAAQAOAA + AAAA4AAAAAAQAMAAAAAA4AAAAAAQAMAAAAAA4AAAAAAwAMAAAAAA4AAAAAAwAIAAAAAA4AAAAABwAIAA + AAAA4AAAAABwAIAAAAAA4AAAAADwAIAAAAAA4AAAAAHwAAAAAAAA4AAAAAPwAAAAAAAA4AAAAAfwAAAA + AAAA4AAAAA/wAAAAAAAA4AAAAB/wAAAAAAAA4AAAAH/wAAAAAAAA4AAAAP/wAAAAAAAA4AAAB//wAP// + ///////////wAP/////////////wAP/////////////wAAAAAAAA4AAAAAAQAAAAAAAA4AAAAAAQAAAA + AAAA4AAAAAAQAAAAAAAA4AAAAAAQAAAAAAAA4AAAAAAQAAAAAAAA4AAAAAAQAAAAAAAA4AAAAAAQAIAA + AAAA4AAAAAAQAIAAAAAA4AAAAAAwAIAAAAAA4AAAAAAwAIAAAAAA4AAAAAAwAMAAAAAA4AAAAAAwAMAA + AAAA4AAAAABwAMAAAAAA4AAAAABwAOAAAAAA4AAAAABwAOAAAAAA4AAAAADwAPAAAAAA4AAAAADwAPAA + AAAA4AAAAAHwAPgAAAAA4AAAAAHwAPgAAAAA4AAAAAPwAPwAAAAA4AAAAAfwAPwAAAAA4AAAAAfwAP4A + AAAA4AAAAA/wAP8AAAAA4AAAAB/wAP8AAAAA4AAAAB/wAP+AAAAA4AAAAD/wAP/AAAAA4AAAAH/wAP/g + AAAA4AAAAP/wAP/wAAAA4AAAAf/wAP/4AAAA4AAAA//wAP/8AAAA4AAAB//wAP/+AAAA4AAAD//wAP// + gAAA4AAAP//wAP//wAAA4AAAf//wAP//8AAA4AAB///wAP///AAA4AAD///wAP///wAA4AAP///wAP// + /8AA4AB////wAP////gA4AP////wAP////+A4D/////wACgAAABgAAAAwAAAAAEAIAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ0BygieAcoInn3KC + J79ygifvcoIn/3KCJ/9ygif/coInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIncHKCJ69ygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn33KCJ79ygieAcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInYHKC + J59ygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoIncHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J99ygiePcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInYHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ59ygicgAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ59ygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicgAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInIHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInzwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicgAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + JxBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + JzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygievcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInQHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J98AAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAAAA + AABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJzAAAAAAAAAAAHKCJxBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAA + AAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInEAAAAAAAAAAAcoInEHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygieAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J99ygicQAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAA + AABygicQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KC + J0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ59ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJ49ygidQcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygie/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ79ygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ59ygieAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J4BygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0BygicQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJxAAAAAAcoIn33KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn3wAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAcoInEHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInEAAAAAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidgAAAAAAAAAAAAAAAAAAAAAHKCJxBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygie/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J68AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIncHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIncAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygie/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJyBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ1AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInz3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygieAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ1AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygie/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn73KCJ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KC + J0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ79ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoInYHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievcoInYHKCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInYHKCJ59ygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygiefcoInYHKC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ0BygieAcoInn3KC + J79ygifvcoIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ+9ygie/coInn3KC + J4BygidAcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////+AH//8AA////////gAP//gAA + H//////8AAP/+AAAB//////wAAP/8AAAA//////AAAP/4AAAAP////8AAAP/gAAAAH////wAAAP/AAAA + AD////gAAAP+AAAAAB///+AAAAP+AAAAAA///8AAAAP8AAAAAA///4AAAAP4AAAAAAf//wAAAAP4AAAA + AAP//AAAAAPwAAAAAAP/+AAAAAPwAAAAAAH/8AAAAAPgAAAAAAH/8AAAAAPgAAAAAAH/4AAAAAPAAAAA + AAD/wAAAAAPAAAAAAAD/gAAAAAPAAAAAAAD/AAAAAAPAAAAAAAD/AAAAAAPAAAAAAAD+AAAAAAPAAAAA + AAD8AAAAAAPAAAAAAAD8AAAAAAPAAAAAAAD4AAAAAAPAAAAAAAD4AAAAAAPAAAAAAADwAAAAAAPAAAAA + AADwAAAAAAPAAAAAAADgAAAAAAPAAAAAAADgAAAAAAPAAAAAAADAAAAAAAPAAAAAAAHAAAAAAAPAAAAA + AAHAAAAAAAPAAAAAAAGAAAAAAAPAAAAAAAOAAAAAAAPAAAAAAAOAAAAAAAPAAAAAAAeAAAAAAAPAAAAA + AA8AAAAAAAPAAAAAAB8AAAAAAAPAAAAAAB8AAAAAAAPAAAAAAD8AAAAAAAPAAAAAAP8AAAAAAAPAAAAA + Af8AAAAAAAPAAAAAA/8AAAAAAAPAAAAAD/8AAAAAAAPAAAAAP/8AAAAAAAPAAAAA//////////////// + //////////////////////////////////////////////////8AAAAAAAPAAAAAAAAAAAAAAAPAAAAA + AAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAA + AAAAAAAAAAPAAAAAAAAAAAAAAAPAAAAAAACAAAAAAAPAAAAAAAGAAAAAAAPAAAAAAAGAAAAAAAPAAAAA + AAGAAAAAAAPAAAAAAAHAAAAAAAPAAAAAAAPAAAAAAAPAAAAAAAPAAAAAAAPAAAAAAAPgAAAAAAPAAAAA + AAfgAAAAAAPAAAAAAAfwAAAAAAPAAAAAAA/wAAAAAAPAAAAAAA/4AAAAAAPAAAAAAB/4AAAAAAPAAAAA + AB/8AAAAAAPAAAAAAD/8AAAAAAPAAAAAAD/+AAAAAAPAAAAAAH//AAAAAAPAAAAAAP//AAAAAAPAAAAA + AP//gAAAAAPAAAAAAf//wAAAAAPAAAAAA///4AAAAAPAAAAAB///4AAAAAPAAAAAD///8AAAAAPAAAAA + D///+AAAAAPAAAAAH////AAAAAPAAAAAP////wAAAAPAAAAA/////4AAAAPAAAAB/////8AAAAPAAAAD + /////+AAAAPAAAAH//////gAAAPAAAAf//////wAAAPAAAA///////8AAAPAAAD////////AAAPAAAP/ + ///////wAAPAAA/////////8AAPAAD//////////gAPAAf//////////+APAH/////8oAAAAgAAAAAAB + AAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInQHKCJ4BygiePcoInv3KCJ79ygif/coIn/3KC + J/9ygif/coInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J1BygieAcoInv3KCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJ79ygiePcoInUHKC + JyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ4BygievcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoInYHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygidwcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygidgcoInr3KCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInn3KCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInr3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ2BygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J79ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ59ygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievcoInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInYHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoInUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInz3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJzBygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicgcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInrwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygieAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInv3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ1AAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ68AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIngAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJ2AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInz3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInj3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInv3KCJ49ygidgcoInIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInvwAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAA + AABygicgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicgAAAAAAAAAABygifvcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn7wAA + AAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAAAAAAAAAAAAcoIngHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J4AAAAAAAAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAAAAAAAAAAAAAAAAAABygifvcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ69ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInrwAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidgAAAAAAAAAAAAAAAAAAAAAAAAAABygicQcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygievAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInn3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygie/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ98AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiffAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygieAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicgcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygifPcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygiefcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiefcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAA + AAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygicgAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygieAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAA + AAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIngAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JyBygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInv3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn33KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJ0AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn33KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J2Bygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoInn3KCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInn3KC + JzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ2BygievcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAA + AABygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygievcoInYHKCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoInYHKCJ69ygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ69ygidgcoInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIngHKC + J69ygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygievcoIngHKC + J0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoInQHKCJ4BygiePcoInv3KC + J79ygif/coIn/3KCJ/9ygif/AAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coInv3KC + J79ygiePcoIngHKCJ0BygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///////////////3// + ///////////gA////wAAf//////////+AAf///gAAA//////////4AAH///gAAAD/////////wAAB/// + gAAAAP////////wAAAf//wAAAAA////////wAAAH//wAAAAAH///////wAAAB//4AAAAAA///////wAA + AAf/8AAAAAAD//////wAAAAH/+AAAAAAAf/////4AAAAB//AAAAAAAD/////4AAAAAf/gAAAAAAA//// + /8AAAAAH/wAAAAAAAH////8AAAAAB/4AAAAAAAA////+AAAAAAf+AAAAAAAAH////AAAAAAH/AAAAAAA + AB////gAAAAAB/wAAAAAAAAP///wAAAAAAf4AAAAAAAAD///wAAAAAAH+AAAAAAAAAf//4AAAAAAB/AA + AAAAAAAH//+AAAAAAAfwAAAAAAAAA///AAAAAAAH4AAAAAAAAAP//gAAAAAAB+AAAAAAAAAD//wAAAAA + AAfgAAAAAAAAAf/4AAAAAAAHwAAAAAAAAAH/8AAAAAAAB8AAAAAAAAAB//AAAAAAAAfAAAAAAAAAAf/g + AAAAAAAHwAAAAAAAAAH/wAAAAAAAB8AAAAAAAAAB/8AAAAAAAAfAAAAAAAAAAf+AAAAAAAAHwAAAAAAA + AAH/AAAAAAAAB8AAAAAAAAAB/wAAAAAAAAfAAAAAAAAAAf4AAAAAAAAHwAAAAAAAAAH+AAAAAAAAB8AA + AAAAAAAB/AAAAAAAAAfAAAAAAAAAAfwAAAAAAAAHwAAAAAAAAAH4AAAAAAAAB8AAAAAAAAAB+AAAAAAA + AAfAAAAAAAAAAfAAAAAAAAAHwAAAAAAAAAHwAAAAAAAAB8AAAAAAAAAD4AAAAAAAAAfAAAAAAAAAA+AA + AAAAAAAHwAAAAAAAAAPgAAAAAAAAB8AAAAAAAAAHwAAAAAAAAAfAAAAAAAAAB8AAAAAAAAAHwAAAAAAA + AA/AAAAAAAAAB8AAAAAAAAAPwAAAAAAAAAfAAAAAAAAAH4AAAAAAAAAHwAAAAAAAAD+AAAAAAAAAB8AA + AAAAAAA/gAAAAAAAAAfAAAAAAAAAf4AAAAAAAAAHwAAAAAAAAP8AAAAAAAAAB8AAAAAAAAH/AAAAAAAA + AAfAAAAAAAAD/wAAAAAAAAAHwAAAAAAAB/8AAAAAAAAAB8AAAAAAAA//AAAAAAAAAAfAAAAAAAA//wAA + AAAAAAAHwAAAAAAAf/8AAAAAAAAAB8AAAAAAAf//AAAAAAAAAAfAAAAAAAf//wAAAAAAAAAHwAAAAAAf + //8AAAAAAAAAB8AAAAAA//////////////////////////////////////////////////////////// + //////////////////////////////////////////////////8AAAAAAAAAB8AAAAAAAAABAAAAAAAA + AAfAAAAAAAAAAQAAAAAAAAAHwAAAAAAAAAEAAAAAAAAAB8AAAAAAAAABAAAAAAAAAAfAAAAAAAAAAQAA + AAAAAAAHwAAAAAAAAAEAAAAAAAAAB8AAAAAAAAABAAAAAAAAAAfAAAAAAAAAAQAAAAAAAAAHwAAAAAAA + AAEAAAAAAAAAB8AAAAAAAAABgAAAAAAAAAfAAAAAAAAAA4AAAAAAAAAHwAAAAAAAAAOAAAAAAAAAB8AA + AAAAAAADgAAAAAAAAAfAAAAAAAAAA8AAAAAAAAAHwAAAAAAAAAfAAAAAAAAAB8AAAAAAAAAHwAAAAAAA + AAfAAAAAAAAAB8AAAAAAAAAHwAAAAAAAAAfgAAAAAAAAB8AAAAAAAAAP4AAAAAAAAAfAAAAAAAAAD+AA + AAAAAAAHwAAAAAAAAA/wAAAAAAAAB8AAAAAAAAAf8AAAAAAAAAfAAAAAAAAAH/gAAAAAAAAHwAAAAAAA + AD/4AAAAAAAAB8AAAAAAAAA//AAAAAAAAAfAAAAAAAAAf/wAAAAAAAAHwAAAAAAAAH/+AAAAAAAAB8AA + AAAAAAD//gAAAAAAAAfAAAAAAAAA//8AAAAAAAAHwAAAAAAAAf//AAAAAAAAB8AAAAAAAAH//4AAAAAA + AAfAAAAAAAAD///AAAAAAAAHwAAAAAAAB///wAAAAAAAB8AAAAAAAAf//+AAAAAAAAfAAAAAAAAP///w + AAAAAAAHwAAAAAAAH///8AAAAAAAB8AAAAAAAB////gAAAAAAAfAAAAAAAA////8AAAAAAAHwAAAAAAA + f////gAAAAAAB8AAAAAAAP////8AAAAAAAfAAAAAAAH/////gAAAAAAHwAAAAAAD/////4AAAAAAB8AA + AAAAA//////AAAAAAAfAAAAAAAf/////8AAAAAAHwAAAAAAf//////gAAAAAB8AAAAAAP//////8AAAA + AAfAAAAAAH///////gAAAAAHwAAAAAD///////8AAAAAB8AAAAAB////////wAAAAAfAAAAAB/////// + /+AAAAAHwAAAAA/////////4AAAAB8AAAAA//////////AAAAAfAAAAAf/////////8AAAAHwAAAAf// + ////////wAAAB8AAAAf///////////AAAAfAAAAf///////////8AAAHwAAAf////////////wAAB8AA + Af/////////////gAAfAAA///////////////gAHwAD////////////////gB8AP////////KAAAAJYA + AAAsAQAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ2BygieAcoInn3KC + J79ygifPcoIn/3KCJ/9ygif/coIn/3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJyBygidgcoInj3KCJ79ygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffcoInv3KCJ49ygidgcoInIAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0BygidwcoInr3KCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1BygiefcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygiefcoInUAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInMHKCJ3Bygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJyBygiePcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygiePcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J49ygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicgcoInn3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ59ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ49ygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J59ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInn3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygieAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoIngHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIngAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidgcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ58AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidgcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInnwAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInYHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInQHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInMHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInr3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygie/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ+9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidQAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInnwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInIAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIncHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInvwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidgAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ2AAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoInYAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygicwAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ59ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInn3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ79ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygidAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygifvcoIngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJ4BygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J89ygidgcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ79ygidwcoInIAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygie/coInn3KCJ4BygidAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJ79ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInv3KCJ59ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInn3KCJ4Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIngHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAABygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/AAAAAAAAAABygiffcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAABygievcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygievAAAAAAAAAABygidwcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidwAAAAAAAAAABygidAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygidAAAAAAAAAAAAAAAAAcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/8AAAAAAAAAAAAAAAAAAAAAcoInv3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAcoIncHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ3AAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJzBygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicgcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygicgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInv3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ78AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInYHKC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn3wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + J4Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JxBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygieAcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygieAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoIngHKCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJ2Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInYAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/AAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicwcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygicwAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInj3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ48AAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJxAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0Bygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInQAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInz3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ88AAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ1Bygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInr3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ68AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInEHKCJ89ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coInz3KCJxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygicwcoIn73KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ+9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn73KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKC + JzBygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygifvcoInMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygicwcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J+9ygicwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInMHKCJ99ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KC + JzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAHKCJxBygifPcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifPcoInEAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoInr3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ69ygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ49ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInjwAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygidQcoIn73KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ+9ygidQAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKCJ89ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coInz3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABygiePcoIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygiePAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ99ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn33KCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygiePcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiePcoInEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J79ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coInv3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AABygidgcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J99ygidgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInEHKCJ4BygifvcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygifvcoIngHKC + JxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAABygicQcoIngHKCJ+9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn73KCJ4BygicQAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJxBygieAcoIn33KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ99ygieAcoInEAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ2Bygie/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coInYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInIHKCJ49ygiffcoIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygiffcoInj3KCJyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInMHKC + J49ygiffcoIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygiffcoInj3KCJzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAcoInMHKCJ3Bygie/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygie/coIncHKC + JzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ0BygidwcoInr3KCJ99ygif/coIn/3KCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygif/coIn/3KCJ/9ygif/coIn/3KCJ/9ygif/coIn33KCJ69ygidwcoInQAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcoInQHKCJ2BygieAcoInn3KC + J79ygifPcoIn/3KCJ/9ygif/coIn/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHKCJ/9ygif/coIn/3KC + J/9ygifPcoInv3KCJ59ygieAcoInYHKCJ0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/////////8AH////+AAB////wA//////// + /4AA/////AAAA////AD////////4AAD////gAAAAf//8AP///////8AAAP///4AAAAAf//wA///////+ + AAAA///+AAAAAAf//AD///////gAAAD///wAAAAAA//8AP//////wAAAAP//8AAAAAAA//wA//////8A + AAAA///gAAAAAAB//AD//////AAAAAD//8AAAAAAAD/8AP/////4AAAAAP//gAAAAAAAH/wA/////+AA + AAAA//8AAAAAAAAP/AD/////gAAAAAD//gAAAAAAAAf8AP////8AAAAAAP/8AAAAAAAAA/wA/////gAA + AAAA//gAAAAAAAAB/AD////4AAAAAAD/8AAAAAAAAAD8AP////AAAAAAAP/wAAAAAAAAAPwA////4AAA + AAAA/+AAAAAAAAAAfAD///+AAAAAAAD/4AAAAAAAAAA8AP///wAAAAAAAP/AAAAAAAAAADwA///+AAAA + AAAA/4AAAAAAAAAAHAD///wAAAAAAAD/gAAAAAAAAAAcAP//+AAAAAAAAP8AAAAAAAAAAAwA///wAAAA + AAAA/wAAAAAAAAAADAD//+AAAAAAAAD/AAAAAAAAAAAMAP//wAAAAAAAAP4AAAAAAAAAAAQA//+AAAAA + AAAA/gAAAAAAAAAABAD//4AAAAAAAAD+AAAAAAAAAAAEAP//AAAAAAAAAP4AAAAAAAAAAAAA//4AAAAA + AAAA/AAAAAAAAAAAAAD//AAAAAAAAAD8AAAAAAAAAAAAAP/8AAAAAAAAAPwAAAAAAAAAAAAA//gAAAAA + AAAA/AAAAAAAAAAAAAD/8AAAAAAAAAD8AAAAAAAAAAAAAP/gAAAAAAAAAPwAAAAAAAAAAAAA/+AAAAAA + AAAA/AAAAAAAAAAAAAD/wAAAAAAAAAD8AAAAAAAAAAAAAP/AAAAAAAAAAPwAAAAAAAAAAAAA/4AAAAAA + AAAA/AAAAAAAAAAAAAD/AAAAAAAAAAD8AAAAAAAAAAAAAP8AAAAAAAAAAPwAAAAAAAAAAAAA/gAAAAAA + AAAA/AAAAAAAAAAAAAD+AAAAAAAAAAD8AAAAAAAAAAAAAPwAAAAAAAAAAPwAAAAAAAAAAAAA/AAAAAAA + AAAA/AAAAAAAAAAAAAD8AAAAAAAAAAD8AAAAAAAAAAAAAPgAAAAAAAAAAPwAAAAAAAAAAAQA+AAAAAAA + AAAA/AAAAAAAAAAABADwAAAAAAAAAAD8AAAAAAAAAAAEAPAAAAAAAAAAAPwAAAAAAAAAAAwA8AAAAAAA + AAAA/AAAAAAAAAAADADgAAAAAAAAAAD8AAAAAAAAAAAMAOAAAAAAAAAAAPwAAAAAAAAAABwA4AAAAAAA + AAAA/AAAAAAAAAAAHADAAAAAAAAAAAD8AAAAAAAAAAA8AMAAAAAAAAAAAPwAAAAAAAAAADwAwAAAAAAA + AAAA/AAAAAAAAAAAfADAAAAAAAAAAAD8AAAAAAAAAAD8AIAAAAAAAAAAAPwAAAAAAAAAAPwAgAAAAAAA + AAAA/AAAAAAAAAAB/ACAAAAAAAAAAAD8AAAAAAAAAAP8AIAAAAAAAAAAAPwAAAAAAAAAB/wAgAAAAAAA + AAAA/AAAAAAAAAAP/AAAAAAAAAAAAAD8AAAAAAAAAB/8AAAAAAAAAAAAAPwAAAAAAAAAP/wAAAAAAAAA + AAAA/AAAAAAAAAB//AAAAAAAAAAAAAD8AAAAAAAAAP/8AAAAAAAAAAAAAPwAAAAAAAAD//wAAAAAAAAA + AAAA/AAAAAAAAA///AAAAAAAAAAAAAD8AAAAAAAAH//8AAAAAAAAAAAAAPwAAAAAAAB///wAAAAAAAAA + AAAA/AAAAAAAA////AAAAAAAAAAAAAD8AAAAAAA////8AP////////////////////////wA//////// + /////////////////AD////////////////////////8AP////////////////////////wA//////// + /////////////////AD////////////////////////8AAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAA + AAAA/AAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAA + AAAA/AAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAAAAAAAAA + AAAA/AAAAAAAAAAAAAAAAAAAAAAAAAD8AAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAAAAAAAgAAAAAAA + AAAA/AAAAAAAAAAABACAAAAAAAAAAAD8AAAAAAAAAAAEAIAAAAAAAAAAAPwAAAAAAAAAAAQAgAAAAAAA + AAAA/AAAAAAAAAAABACAAAAAAAAAAAD8AAAAAAAAAAAEAMAAAAAAAAAAAPwAAAAAAAAAAAwAwAAAAAAA + AAAA/AAAAAAAAAAADADAAAAAAAAAAAD8AAAAAAAAAAAMAMAAAAAAAAAAAPwAAAAAAAAAAAwA4AAAAAAA + AAAA/AAAAAAAAAAAHADgAAAAAAAAAAD8AAAAAAAAAAAcAOAAAAAAAAAAAPwAAAAAAAAAABwA8AAAAAAA + AAAA/AAAAAAAAAAAPADwAAAAAAAAAAD8AAAAAAAAAAA8APAAAAAAAAAAAPwAAAAAAAAAADwA+AAAAAAA + AAAA/AAAAAAAAAAAfAD4AAAAAAAAAAD8AAAAAAAAAAB8APwAAAAAAAAAAPwAAAAAAAAAAPwA/AAAAAAA + AAAA/AAAAAAAAAAA/AD8AAAAAAAAAAD8AAAAAAAAAAD8AP4AAAAAAAAAAPwAAAAAAAAAAfwA/gAAAAAA + AAAA/AAAAAAAAAAB/AD/AAAAAAAAAAD8AAAAAAAAAAP8AP8AAAAAAAAAAPwAAAAAAAAAA/wA/4AAAAAA + AAAA/AAAAAAAAAAH/AD/wAAAAAAAAAD8AAAAAAAAAA/8AP/AAAAAAAAAAPwAAAAAAAAAD/wA/+AAAAAA + AAAA/AAAAAAAAAAf/AD/4AAAAAAAAAD8AAAAAAAAAB/8AP/wAAAAAAAAAPwAAAAAAAAAP/wA//gAAAAA + AAAA/AAAAAAAAAB//AD//AAAAAAAAAD8AAAAAAAAAP/8AP/8AAAAAAAAAPwAAAAAAAAA//wA//4AAAAA + AAAA/AAAAAAAAAH//AD//wAAAAAAAAD8AAAAAAAAA//8AP//gAAAAAAAAPwAAAAAAAAH//wA//+AAAAA + AAAA/AAAAAAAAAf//AD//8AAAAAAAAD8AAAAAAAAD//8AP//4AAAAAAAAPwAAAAAAAAf//wA///wAAAA + AAAA/AAAAAAAAD///AD///gAAAAAAAD8AAAAAAAAf//8AP///AAAAAAAAPwAAAAAAAD///wA///+AAAA + AAAA/AAAAAAAAf///AD///8AAAAAAAD8AAAAAAAD///8AP///4AAAAAAAPwAAAAAAAf///wA////4AAA + AAAA/AAAAAAAH////AD////wAAAAAAD8AAAAAAA////8AP////gAAAAAAPwAAAAAAH////wA/////gAA + AAAA/AAAAAAB/////AD/////AAAAAAD8AAAAAAP////8AP////+AAAAAAPwAAAAAB/////wA/////+AA + AAAA/AAAAAAf/////AD/////+AAAAAD8AAAAAH/////8AP/////8AAAAAPwAAAAA//////wA//////8A + AAAA/AAAAAP//////AD//////8AAAAD8AAAAD//////8AP//////+AAAAPwAAAB///////wA///////+ + AAAA/AAAAf///////AD////////AAAD8AAAP///////8AP////////gAAPwAAH////////wA//////// + /4AA/AAH/////////AD//////////AD8AP/////////8AIlQTkcNChoKAAAADUlIRFIAAAEAAAABAAgG + AAAAXHKoZgAAD5hJREFUeJzt3c1VG8kaxvHHnAlAN4OeVS8tR4AcwcgRWEQA7gCE3QEIHAE4AjMRICIw + s9TKCoGJYO6iq0UD+mx1ffb/d849eJhBqotVT7/1VrX0TohWXhaZpEzSQNLQfPu08Z+MXv/MYjp7t+Mx + /1vz7Xnjzw/m66OkJ0nLxXS23GO4CNAfvgeA3fKyGKqa4Jmk9+brcMuPdG204c+SpLwspCoQlpL+MV8f + F9PZo/WR4SgEQGAak/3UfHU50Y9Rj3Vcf6MRDI+qKgdCITAEgGd5WYxUXVXrCT/wOR4L6mCYSFJeFk96 + DoT5YjqbexsZCADXzLp9rGrCj5TehN9loOr/90jSpQmEuapAuKOf4NbWhhC6Ycr6z6omfuZzLC2bgC4t + Jd1J+sFywT4qAEtCmvSRySRdSLrIy2IpwsAqAqBDprw/F5O+K5nehsF3lgndIQCOlJfFQNWEP1c8HfsY + ZXoOg0dJ31X1DJ68jipyBEBLpsSvr/Z9a+T5NpR0I+kqL4u6KmCJ0AIBcKC8LOqr/cjzUFAF70TSJC+L + uaoguPM6osgQAHvKy2Ii6VKs7UM1kjQyvYJvi+ns1utoIkEAbGHW9xeqrviU+XHIJN3kZXGlqk9wTZ9g + MwJgDSZ+EgaqKrbzvCwIgg1OfA8gNHlZfJX0W9WLh8kfvzoIfpu/WzRQARis8ZM3UHX0+LPoEaz0PgDM + zThXYg+/LzJVPYJzSV/6fjNSbwPAnNq7UuP2VfTKUNK9OUfwpa+nC3vZAzBrwV9i8qN6Dfzqa3+gVxUA + 5T42qPsDf6lny4JeBIDZ1rtUtbUHbFIvC65VNQqT3zZMfglgrvq/xOTH/i5ULQtGvgdiW7IVAFd9HClT + D6qBJCsAc6fevZj8ON6FqiBIsm+UXADkZXGhquRP8i8MXgxVLQmSu6AkswQwJf+N2NqDPVd5WZxKOktl + SZBEBWDKM/b14UJ9biCJCjP6ADBn+H+JM/xwJ1MVAhPP4zha1AGQl8WNqrIf8OHGvAajFWUPwKz370Wj + D/5NzHLgY4x9gegqgMYWH5MfoahPEEb3mowqAJj8CFiUIRBNADSafbxLD0I1UGTNwSgCwPxCo262oFdu + YgmB4APAnL5i8iM2NzGcHAw6AMwWy5XvcQAtXYW+TRhsAJhf3MT3OIAjTUIOgSADgMmPxAQbAsEFAJMf + iQoyBIIKACY/EhdcCAQTAKZjOvE9DsCySUi7A0EEgNkzpduPvrgK5ZyA9wDgkA96KojDQl4DwJybZvKj + r2583zvgLQAaN/YAfeb1BiIvAdB4/z5u7EHfDVRVAl7mgq8KgFt6gWfeqmHnAWD2QZn8wEtDH2cEnAaA + 6XpOXD4nEJGJ650BZwFAxx/Yi9OdAScBYBocP108F5CAn66agq4qgBvxvv3AvjI5qpatB4A598wn9gCH + Gbu4Z8BqAJi1DGf8gXaubPcDrAVA47APgPasHhKyWQFciv1+4FhDVXPJCisBkJfFSFIw9zwDkbswc6pz + nQcApT9ghZWlgI0K4FJs+QFdy2RhKdBpAFD6A1Z1vhTougJgyw+wq9M51lkA5GXxVXT9AduGZq51opMA + yMsik3TexWMB2OnczLmjdVUBXIl39wFcGaijpcDRAWCaEpz1B9wad9EQ7KICoPEH+HH03DsqAMy7l9D4 + A/wYHvsOQsdWANbOKAPYy1FzsHUAmK2I7JgnB3C07JhtwVYBYM4ks+0HhOG87X0CbSuAC7HtB4RioJZH + 8A8OAK7+QJBaVQFtKgCu/kB4WlUBbQKAqz8QpoPn5kEBYPYcufoDYRocei7g0AqAfX8gbAfN0b0DIC+L + sdj3B0KXmbm6l0MqANb+QBz2nqt7BYC593jUcjAA3Brt+34B+1YArP2BuOw1Z3cGgDlcwP3+QFzG+xwM + 2qcCGIutPyA2e1249wkAmn9AnHbO3a0BYBoJvOEHEKfhrmbgrgqAqz8Qt61zeFcA0PwD4rZ1Dm8MgLws + huLkHxC7zMzltbZVAJ8tDAaAexvn8rYAoPwH0rBxLq8NAMp/ICkblwGbKgDKfyAta+f0pgCg/AfSsnZO + vwkAc3AgszwYAG5l6w4FrasAuPoDaXozt9cFwKmDgQBw783cXhcAI/vjAODB6PU3XgSA+bxxbv0F0jQw + c3zldQUwEoCUjZr/8DoAWP8DaXsxx18HAPf+A2l7McdXAWCOCrL+B9I2aB4LblYAXP2BflgbAKz/gX5Y + zXUqAKB/WAIAPfYyALa9ZRCA9NRzvq4ACACgX14EQOZvHAA8yKTnAHjvbxwAPHgvUQEAfZVJ9ACAvqp6 + ALs+OwxAmvKyGJyI8h/oq+GJuAEI6KvBiVj/A3013PXpwAASdiLuAgT66pQKAOgxAgDosRPxTsBAX42o + AIAeIwCAHiMAgB4jAAAAAAAAAAAAAAAAAIAEvMvL4j/fg4A7i+ns3bZ/z+th5UnSo/nzo6R/Jc0lLRfT + 2dLTmDr3h+8BAIEa6PlO2frrpSTlZVGHw4Ok+WI6mzseW2cIAOBwdTiMJF3mZSFV1cHfku5iqhAIAKAb + I/O/q7wsHiX9UARhwM1AQPeGkq4k/c7L4mdeFmPfA9rkD1Wly8jvMIBkjSWN87JYSvou6XYxnT35HdIz + KgDAjUzPVcHXvCyC+EAeAgBwa6BqN+F3XhYXvgdzomorA4BbA1UNw98+ewRUAIBfmaSfplmYuX7yEz2f + dgLgz1jSL9fLghNVRx4B+FcvC+5dNQlPJC1dPBGAvY1UNQlHtp/oJPSTSkBPDSTd52Xx1eaT1E1A+gBA + mC5Ng9DKkqAOgKWNBwfQibGqaqDzEKgD4J+uHxhAp4aqdgmGXT4oFQAQj0xVJdBZCNADAOJSNwc7CYET + SVpMZwQAEI/OQqB5FJgQAOJRh0B2zIMQAEC8BqruI2i9O9AMAO4KBOIzlHTf9oepAID4DfOyuGnzg6sA + MI1AbgwC4jTJy2Jy6A+9fj8AqgAgXleH7gy8DgD6AEC8BpJuDmkKvg6AeafDAeDaUOYTjPbxIgDMRxzR + BwDidrHvewmse0/AeadDAeDDXrsC6wKAPgAQv2yfNxNZFwB33Y8FgAfnu44KvwkA8xZhSzvjAeBQ/SEk + G236XACqACANk21VwKYA+GFnLAA82FgFrA0Acyx4aWs0AJzaWAVs+2gwlgFAOtZWAdsCgGUAkI61VcDG + AGAZACRn8vobuz4dmGUAkI7Pr7+xKwC+WxoIAPeyvCzGzW9sDQBzKIj3CADS8VfzH3ZVABJVAJCS/SsA + 407cIgykYtBcBuwMgMV09iSagUBKVsuAfSoASfpmaSAA3Nu/ApBWzcC5pcEAcGtQv3novhWARDMQSMlI + OiAAFtPZnTgZCKTiVDqsApDoBQCpOHgJoMV0diu2BIEUZHlZDA6tACR6AUAqhm0C4FpUAUAKDg8AczCI + KgCIX6slgEQVAKTgtFUAUAUAaWhbAWgxnX0V5wKAmLVqAjZxLgCIV+segKTVuQDeMASI1LEVgCR96eAx + AHhwdAAsprO5eL8AIEpdVABSVQWwLQhEppMAMO8XwLYgEJmuKoB6W5CGIBCRzgLAoCEIRKTTADANwesu + HxOAPV1XAFJ1OGhp4XHhBs3cHuk8AMx9AmddPy6coY/TH082KgCWAkAcHq0EgPFNXE1ixN9Zj1gLAJYC + 0frX9wDgzIPNCkCL6exRbA3GZu57AHDGTg+gaTGdXYt7BWKy9D0AOGO1B9B0Jl5YUTDHutkK7Ac3AWD6 + AZ9cPBc6QSMwfcvFdGZ/CVAz/QCagnF48D0AWPcoWdwFWMe8g9Cty+dEK3PfA4B1D5LjAJCkxXR2JkrM + oJmDXEjbXPIQAMZHEQKhm/seAKx5MktyPwHQOCREtzlcf/seAKxZbcv7qgDqpuBHX8+PnTi7ka5Vk9db + AEjsDITMnAdgmZYm/xVAzewMEAJh+uF7AOjcnVmCSwogAKRVCHDPQHhYBqTnRagHEQDS6p6BW9/jwDOz + DCAE0vG0mM5e/H0GEwDS6ozAre9x4AWWAem4ff2NoAJAIgRCY64YS9/jQCfefHZHcAEgEQIB4kNf4ndn + lnQvBBkAEiEQmFtxaCt2a0M82ACQViHA7oBnZtuIKiBe8033dwQdANJqd4BzAv5diyogVt82/YvgA0Di + sFAITBWw8YWEYG28+kuRBIC0CoEP4irkjanGlr7HgYNsvXBGEwDSixuIOKPuDz2ZeFyv6/w3RRUAEiHg + mzkXwOnA8O21ZIsuAKRqPbqYzj6IbUJfvoilWOjOmjf9bBJlANTMNiHNQcdMWUlDMFx3r8/8bxJ1AEgv + moNLvyPpF9MQnPseB9446CP5og8AadUX+CDWpq59EkuB0OxV+teSCABp1Rf4JLrUzvCBL8G53rf0ryUT + ADVTmn4QuwROmEMm9AP8e1xMZwdf/JILAOnFVuG177H0wWI6+yqWXz49qeUb7CYZANJqSfBF1S9m6Xk4 + fcAHvvjxJOnjIev+pmQDoGZK1A+iGrDKvAAJW/c+1R/y0UbyASC9qQa4SlnSaAqyM+DG2bEf4/auo4FE + JS+Lr5LOJQ08D8W5xXRm/e88L4uhpHv18Pfr0Jk5A3OUXlQAr5mmFecGLGk0YakE7Ohk8ks9rQCa8rIY + SbqSNPQ8FCdcVAA1KgErOpv8Uk8rgKbFdDY3NxadiQZWpxonNOm7HK/u9t92+aC9rwBeS70/4LICqOVl + MVBVCfSiyrKgnvydBykBsIZ5wV4owSDwEQC1vCxuJE18PX+kHnXEPv8uBMAWKQaBzwCQpLwsJqp6Lkn8 + Pi27bnO89xAEwJ7MC/dSUuZ3JMfxHQDSqjl4I5YEmzypavZZ36XqfRNwX4vp7HYxnf2p6qDL3PNwosa9 + GlvdSfrTxeSXqABay8siU1URjBVRORtCBdBktmFvFHll1QFnV/2moF4MMTJ9grGqPkHwJW1oAVBLffdl + h2tJ32w1+rYJ8sUQK1MVnKsKhMzrYDYINQCkF1XVxO9InJmruuovfQ0g2BdD7Eyj67MCC4OQA6DWgyCY + q7rizz2PgwBwIaQwiCEAagkGwZ2k7yFM/Fo0L4ZUmBf1WNKppJEcr3ljCoCa+Z1NVIVo5nMsLTyp+vyK + 7z5L/U2iezGkxnTBR6oCYSjLgRBjADTlZTGW9JfC3325k/TDdVf/UFG/GFJklgtDPQdCpzsLsQdAU2Bh + 8KRq0j+o+mCOKG6FTubFkLJGKGSS3puvrYIhpQBoMr+jkZ6DM7P8lEtV5/QfVH0Ed5R3PCb5YugLcwah + XjbUgXDa+E9Ga37sf7FcnY7R+N3Uv5/693LIMutJz7cyPzT++TGV3+H/AUP4YokDhiozAAAAAElFTkSu + QmCC + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Microsoft.InnerEye.Listener.Wix.Actions.csproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Microsoft.InnerEye.Listener.Wix.Actions.csproj new file mode 100644 index 0000000..915eb23 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Microsoft.InnerEye.Listener.Wix.Actions.csproj @@ -0,0 +1,188 @@ + + + + Debug + x64 + 8.0.30703 + 2.0 + {001C5475-43FA-4348-B218-8F12771B56EE} + Library + Properties + Microsoft.InnerEye.Listener.Wix.Actions + Microsoft.InnerEye.Listener.Wix.Actions + v4.6.2 + 512 + + + + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.CA.targets + + + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + false + + + + ..\packages\Microsoft.Bcl.AsyncInterfaces.1.1.1\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + + ..\packages\Microsoft.Extensions.Configuration.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Configuration.dll + + + ..\packages\Microsoft.Extensions.Configuration.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Configuration.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Configuration.Binder.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Configuration.Binder.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.3.1.9\lib\net461\Microsoft.Extensions.DependencyInjection.dll + + + ..\packages\Microsoft.Extensions.DependencyInjection.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.DependencyInjection.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Logging.dll + + + ..\packages\Microsoft.Extensions.Logging.Abstractions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Logging.Abstractions.dll + + + ..\packages\Microsoft.Extensions.Logging.Configuration.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Logging.Configuration.dll + + + ..\packages\Microsoft.Extensions.Logging.Console.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Logging.Console.dll + + + ..\packages\Microsoft.Extensions.Options.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Options.dll + + + ..\packages\Microsoft.Extensions.Options.ConfigurationExtensions.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Options.ConfigurationExtensions.dll + + + ..\packages\Microsoft.Extensions.Primitives.3.1.9\lib\netstandard2.0\Microsoft.Extensions.Primitives.dll + + + ..\packages\Namotion.Reflection.1.0.11\lib\net45\Namotion.Reflection.dll + + + ..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll + + + + ..\packages\System.Buffers.4.4.0\lib\netstandard2.0\System.Buffers.dll + + + ..\packages\System.ComponentModel.Annotations.4.7.0\lib\net461\System.ComponentModel.Annotations.dll + + + + + + ..\packages\System.Memory.4.5.2\lib\netstandard2.0\System.Memory.dll + + + + + + ..\packages\System.Numerics.Vectors.4.4.0\lib\net46\System.Numerics.Vectors.dll + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.7.1\lib\net461\System.Runtime.CompilerServices.Unsafe.dll + + + + ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll + + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + + + + + True + + + + + + + Form + + + LicenseKeyForm.cs + + + + True + True + Resources.resx + + + PreserveNewest + + + + + + + + + + LicenseKeyForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + {bc82ac8a-90d2-4caa-8997-af4eb8359c8b} + DICOMAnonymizer + + + {dee3a9d7-41c5-4058-8e5e-b913ba71680f} + Microsoft.InnerEye.Azure.Segmentation.Client + + + {49cefc76-ca31-4e06-ab3e-b5d185780bcb} + Microsoft.InnerEye.Gateway.Models + + + {4BD99139-F4BA-4BBD-A235-6E67048922DF} + Microsoft.InnerEye.Listener.Common + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/AssemblyInfo.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a002bfd --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Microsoft InnerEye Gateway Wix Actions")] +[assembly: AssemblyDescription("Custom actions for the Microsoft InnerEye Gateway installer.")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Microsoft InnerEye Gateway")] +[assembly: AssemblyCopyright("© Microsoft Corporation")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("001c5475-43fa-4348-b218-8f12771b56ee")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.Designer.cs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.Designer.cs new file mode 100644 index 0000000..ffc1393 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.InnerEye.Listener.Wix.Actions.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.InnerEye.Listener.Wix.Actions.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.resx b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/Properties/Resources.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/app.config b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/app.config new file mode 100644 index 0000000..dbc2567 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/app.config @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/packages.config b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/packages.config new file mode 100644 index 0000000..7c77aef --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix.Actions/packages.config @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Icon.ico b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..45fdc3ba5ef6e50af80b2a960696214910d8b737 GIT binary patch literal 367863 zcmeHw34k0$)ql-y$c7Nu6+{s7Wd{w4;aUW_4B6pOKm=DL9C8gPa)`LfeT40RBK#2- zfe!(}i4qXPv4V;mff+spj{u8^c%d_ZatN}^Aqip0{C}^f-lV3sd#3N{IR+}P>Q&Wy zzxS(G)jhqlvqB`q`l6>tKqkd;Q-nA$A;f#%TWa4uRfq@H5Msj(OYIBS6ymfQLd=|5 zYX2GaRqG2eYgVcKlXZl6>H|Xb_LkZ=bqjI9HbNwmvJF35?JC4(o0ZzfZG`ck6=Keu zQhVC}2r=(OA-3MS)Sf+Ch)-T5L|>nrzo`%>+$O}1JC@onepHBc{~*NNxuy1AI}5RL zjfCj$m-ACX?3PN16ePvB*=|C7$w-J#ezMdS144Y~{}SSW14`|KzmE309U`4BwQssY zi08l2Aq+##pOz5cd4H$4^2$gtmcv}IiJT*lCqyLPwI0rA7eOPPxGaJ z=4z6S8u(2P?XcItKZ$?x<>lqkzZom}!+fvtkIDHNI}Eaa60dT6M)rGM|0K<)7wk>S zUGgFOto#p}6P16gY}m}7G?&f52Ir&6sWPA1#8Z;TvMd5+;d~(;ObD?M1^pZ3Y%yXCabZWA3835>Lqbbik-%yPq#tt4p6bZgsoVXQcWtz7O*TF&}nzA2+X8cuOG&c)hP% z6rcVA7fBSluNXyP&Jj@Q1zidytMt(rYNvRNl|+9J$}kGsEK44bK3b=;-lCr7v8u#4 z%5N2Kg$){ue!^X$w`r#=Y>(NDHSJ0JBh|(zeOa~-9dTldo99UTPOz8p$T`Doqnh+$ zANG>fY^r@8?NbgqirJ-olYiK*$vgXZX+Pz1ywm#$;v!A9>BZQbNC)F7cT+Sjs+SYo zJcclF-HD87d|3H2D)EPv|AW$gSo=3g_UVPS3i|vRE?F-93;RKypR}Ik>m2)mt`B7l z#r7Pva+X!ZxiW##3GmqLky& z?314QlUDOtSHuH=i5XpDe8Wyr=t+niiXn@DTwU-`pr;G>UpPi9^rbOIPbcPeVtyCw zbc^wxUNO-#WZsA2QLez_)JX5c5VtEnnGcqjPq{+tQs!5;iMD}~M^ST@+JkJTmc-Z; zN>;OB+T*$CqnuK4sQ$Q4{kAbn8x#+ZpClE3*=pmIKJyIac`T)`T6p<-oL1~j!er4` z!L3tYWQXuU*2Jax%yV3FepL?DxpKUdm@3AwKiHz&sOYQK_UaEdlX7mhnti>`t3Nr< zRcz$jqd#-~FyHaIahx9g8FHT6Jo6G?dH*qHH_~N%lpE#q#f={x>(C$ZmZq`wd@*J> zlJL42&tvN}FN*x6vy1PuquPJwy(|3E_1p2;Nn<^(LyG3n3pT<&KeHuIrGMei)fp-= zy5S_H`jhiBPI-0d&s;YPe@^aw%$QvGquei_WnO(c^jFmv`zAanRriPke>C4VF7~BL zjHev3%-bsdXnabw!#8lBYj#8K+dUCQD`1k}$ zk62y$P^U7E_cP;)zC6Z2Kb71RF-WC=vEx$`;}cW5#*0&W$LZ&y_(+Pt$3h}~U*m)e zr(?kF07?d>yauROA@TYo&B=5xJ@r2S3n z=;)fwn8K7*uvX1+hw)n4sPo(?^O@owmGRQLTk=s&?}Mw1oE!CdH9&spg}Cz7VzcU# z^W5^3Hyz4M07w`|WW^rcW7*>V|gN{>@- zOW%aAs(*)m$xcn5i~(bb^Q!CZz%R|W<(7R}Nz9`>&!bF=y^3ENpSH5+RtGzDwq#Js z9B6(?PyOXHp_UK4-<&8e%*|cZY0O;FDQ1j!h$#h>oGeC%5GYf`>M{fMnJ9B5(cu|r4W zhEc|-Un$fk-jezpyEev>EdxcjHOV)|(7IWUgPw6vf7;F;>z&wA#yUPHBo15u=qG*{ ztF0v87)RXHidVNm=y3 zZe9<)`t|6W;!P1|dPU@W6n+L#dP-jF|8GQbd&;M3*!->;Y}X^ti3jpqkNr))$#1>R zHS$}J^PKj%x!)iS6ym{E@sVo=?{LC^LRg~kU5;7tKM;{`%l9_$opIye1Lqlz)k?bY z@6vZwzl_DHe;2+T`BnVW`d7_A2fk^(<@&N8JBfLe|Fr)sF;wwQ<7uAnYbM!1p>t>i zC68k6Ma?(qsh`F<@>yJ1SZ7rtH+3RmbgaO~TgOofC^-~E7QBh3H_}`>P@jsj4&I}$ zQuN$!N+TgUsN^uVfU&SU3-;$tB*eL^5@J6~=qTk${Y z!8hT}tFfa^`Qs`Mn(uawHr_nPp!2eZ%)cwln*WrH!O--MYz7g(*VqZKPygon!%o^u z>;d>Ue})V@BX#gk`=4_!UkCpKw089J>YL_ym``!1Xdb^J@=s?Jos*94TNqmuGNSnH z&XZ(4{S&|NUyuAJKKT5p$Nne(BQpMaoquWR-_x~^@eX@!Xb}8Sy<_+_g4+!6wijC^9oAqN`+Ti$o zOBEk*(HY{)X3+S6C&?I5&&Lg)g3UK~6kEXjmGdtM?n1!F?{n#_^ObizKVbe+(w@Qg z{mKCGm9HPbl=X{?=LdwZ;`$5Qp3h^(#WDbVgr(yCg>45Rec~GdhDrCgLEvH=9`Mnb zn?b4IjO;YZi(?N$Qp`cvVH*u%FHZYcy-t|dH*guZ^N%r_XEgA|@%lj+4Viy&z5bTZ z9AM3(Ft46vfcq^^w)h2Xal8LZGd7k%a7eZSSceMk6nwrp3u14&wc<*ZoD;iAnb_I!=tC^TyLP#Rq@nuY8{9nDn++j2`j1!p2y=VDx~`6E+1S z<)h<0;}Rd)t(k9(rJ95tI(zacRde4VKC+Xp=Fe+iEu1y*g@#Ly2RLbu8z@%Ul4MtvkgQ%N*KdpJf4U^23ti=|!93 zBfk@nLy)T=i;zWpd8{e%<*}w*(bG?oe#V49-6innvtsviCP0epML8&Qu_9-ovpi#< zPofN?(7q~aWmNVhg9re&=p3beV&%*9ZRJPK(XrXJhplWl(JA=KdmTDUKdL=BkGQfN zTSZTEEZ0S~uhua>g_5sgvjT%QH|uns#kf(`R<%0j4qD8wz@_)IJ?JSvs*ZKgUo!__ zBjdo%Bpiej^C*vN=7>Z4Nse8YnC}TA`6XMBuGgUoW=l@}`j9xYRs2p`gBoKe zUO{n5oJe~?@|ib5mT>ycOJ27SaGK{5?Dw&cAY=$QlhS4&`-fu;7^gw@=mq;(_7Q}X zHiO8|it`=Vg1Qcy*aE^y+?CJFfNWN9=hG%}W{?g26~tPrHpsr(c33C==-#D*Lmg`) zv>nDLILAR@#j7^@un#B+j^B-}?wBy?{9% z)Y?mXzH&#wQog5?b^}}cfN>gRk6*wa$NPCYhbCR0pbLakid|VZKkIKWkE)cM`p#3b z830bgF^NmYi@ua!9|7S^F(x|+oOIvfdA$O_IcSGX*8^w9D^{O4Gdh-9ZLe5;;B<|V zV*pmyIp7hejV13JWMz7Hb1UNKdhr{J~a zw+2p{NAZS&&k8$y^+}&u$TH6wPSR69Ve7N9<5WjKRxIZ5bG?bKTsO%=x6wt?SVtsC zijrIPgBqo2Q4${gC{k7WVvPP8c=@=NiNUy8REausAhWD#5L=@hffBdhw*#bDypXd4D8ncfIgWPKeTIJ88yS>5N>MAMqds5z!HOW*q%)dq zm-i#;75=T}kq+bN%pB3jS7~R_m$b9zR1X`3nQT?yv}#+~#5m&GHeM?`Qb%(JtZdfO zVIFank#=h0rg6GW=)B@q{kg&~edgWZ*0G~avAecm;gk4J>bzLBF)nQ}U**BJuG)i* zj8{7r^RlXKt2*X*#!dd%ChAEG|F$tZ9BX_2V7@Qh=5!=^7W?s3Ch4AE_E+U13n{ zx|-(#gNi-tVqQ%=VPjY92g*5dpxCY%rw7i;dlhpt9_ClY6f*XVV&BXCQ9ZGn*Cy~i z5H#mxPvNC%j2?Vv72Cre=GVld*snR(h5w3sG0e-lnClov2-pdC#eEFgf#*7L5is^i zxXFGPoCl0O1AFD`5OWe^?4!UtjFMEbw(Id=fO0d8!qJ6cn{1N(Fl~ozAJ`k=jMQ@@ zl+R1>I)UE@~#dgfoT;Whs}<5qlA{?lG@g+ZC`YM%7vIKSDCorFC)e==_Hvn@As zq>q~C3EOrfVVmw9vQF@@T_3#GviSud=H?| z`xjb|ax6RsI>t_YG=|0waK9gEe)eY-KFd-}OyC19`0j(war(p4#X@I6%%ab@;1eeWnR9^o$UrD}HMIHQ_9r+w0IzJ@ukJ!&cDTGpB>>LzVF7IcHR!*k(}nRNJyX>DdYNp7yGR zZS_55-l{(c9dkqy_UMe{wec<4c91r8TkJ#P&+7et+HBj8jaqXRA9OwU&ADnru^p&g z8y|U9A4EMW{J=U81YX@n1o+MK2YyF%-$Aq^z)xpoPY_tGY=n&8y#B&(D7i!NgpA)H z`|vYt#a$4(5b>AqKY=sQGhdB6B>bcBQNI62o$@G@^Em|k#B~`v*$)dl*$ogsahzs* zs*Evy!aQP!TaBSzuO9<3&Yw`u6gTnms<@fsVQeFvpZb~*b~;maeIV@!_$Tbc4UC_# zH|qTx_50z&2gtCm&VMNVe-m!Zux}^G-0a zeJ5k3%{0#mTf*M}O4a*T#!v%4*%;ulQA_xybBbaaMxnJILn%^WEZmlTG=|2~9M9(p z+b$OI(V~8QZfF9Z&+3I7PplAKkhz5UQKX`gFh8196p0)@NQBQLLE;5zkX#|0J=_DzY8JXJ{GG26gr$|3rk6TMbTQ%~KhJqK-r#=|em9JS$}Wr5o8 z&(e&uTgDAMCJ+6qC3rIMAuW79D`6x^<`M?|F&pq|&>yn_&j$T58+;zlApG<>tsPP5 zunO~YS|%0BKggXvltGjX3gwiUb0n2vRjU|FHYgWIX`V$PJ@tono~Z!gCn^2qA+7M3 zI(3d!o$#V$tJ$#Vqwy&#dwz9f7sWB2YW`ryKrwmnD2J=rchd*|UU;_6PuRQd~Yc6WrOC<#@?i}6ythOe4UdvzSR5-oV!{+ z6>f*^#`@JOKJ+{at<6430zc(t=)S`Aq1)Et3w2+xFC9aeZN+CQOh05>@fBK*=rJfh zLtVQ%#b=(M@EytZR^c+L^)s)3h%dA=Q_Yt;#An{W07G8I5mr6o_{{rH#1iT?LFFpq z_)_E#{s&bIVb&vzuYBK$crszeq}q-sz6|>z3ICyA7dS@t%k2xo8^~`)bR^X255zRLw`-HZ!H` zo2A{3FO+8obQZbFGq2<8uznO<#w%X8^Xd>E#nz1X*PM?OTc6u?@H)N@>(|WhZ}SSD z&G>vmeGk}-&zF+Cek8G{=q%-Z8H!|oVf&$2*cVIaQepd5e1*cFf`Ey_#xsE5_k=48;nYsdM5N^XZ;BU(Jq1AB|5tvF~*Z?DwD;D2}mJ z^Cj(NA&Wi=%}-YIaaBa`ZhqTjY=gW;Awo)jL*Hg!1fTbIVhtjx;@uz-4|?*1Yx&|jjZmo zS{rr(%{A2*Y}4Lw&6$TeY1OY?J?K~or|JvstOx8~_{pn&t?FUKs>Unrd%^B?PRc4y z(g6$Y*HF$7_6hq2idXDj=Gi{S297|^CaZXqc(YdhA?iliw<_UpI7FZ2{TrqaJKJiU zRL8iK-PoY?>@z5PZtSpcmYAD&GvCWt#g~^o=xYx9CJF43oFDAd3s-*UD8A`lF_iOI z$Ef)9wN|=aCvC-dBx_5U6rWMYTRxwGBkzQtAjZ@&Ui12k7zTof!>Nrr#ydd1fivS2 zYY^k=6mR+d88Jk1PBB+?iZ@Mufq~xZMS5=3A>ORwbFj(drQ9t)|3!VIiQ}bME1qAW zoo&*1C&eoFZ=`#dxK;L2SiUJ|%Q?*cnlN6<)sz~aUXMDLLs(ymk?w^n_Ng9cyWJWL zG(kRxb>;Ao#UnREwHt7VylkvQmoBe2w$T6Gu zB#4>rtyvektkeD-Q~YLHAl{4;??4O0JHYXhM7;T^VrIWl`J>p_Crjw)Jx1tzmwYHb zBk@PEu}_xJ^`Jy@ZYe&ah?iord+#5~8b=(j!TziyxCnh8vtRfMdwnPETlUQo7*b*5 zP<(|QZ;E|YlZbaX%$V7Cm_8^5_ESq@ES+7WVc}u_VcD11EzcFqJ(l@v#*fi zCCo|o;YMN}y_X0)X2uXgyoA+!PryFh$}nPP3<1aMJ5~i(pzEyI4eERjELH^@?dQ=T z)3Yyo}B3eCa#IcA}E-YoO5Hag~>h z&+R{HBPdyva%_xS)yXk5mge}nXJGqQvRItJZzcAQ7mJ0i3DE_Ke?(#&zn9om z5Mms^r`VOlpWDLkEp{0~^x^jyOVTiZS+fTZE9jRtd+>XYQz7a1AS>hw*&f$qLAG-? z(vW&7dsvE+p7 zJJ4o(W3rFNB%Rs~dK`SGQ7Bfz!`LQCCw?)%2PIR@j!hr=?{nhA?-=-@bs0vf8ZY|G z9MtlOvBaTcd#Y`v9@@Pte0W_K9BcM3*!8m3WnPuoNjFM3?Y+70fj!z=p4Y|BBWwm4 zH}erl{J=)}okd~0o}^Xmuxa?UTV?*Jl~1QS*d0Leyw)l%-6#4|YJW&|Px~KOD91gm zvDG|_*z;E7LeRN7|KUFqGzK+x*bXz-_4OK2eOIf85jXLu`b0abHD?}@s(-6GFa?_D z6f+n0hc=xdWqm~TLH2JY!^Xru;U^POyehw0_ODA|q3c*6=eb>6>=XTT?NxC_Q6FRf zx&)SN*m&4y*naHnvwu}$Y-oF2#lb#RU6gh9&l0|SP(sahj!*F!MUGGM!TwnSSB9~L zEft@WaE0xg?6ZH8Xw&%?84mUxrBB6I#aRdau+kSg2X&6U;u?pzBiZN7Z=GW=-`fCB znlVKs>l}Mh@jnt(%k+h~#4D{@`N9{FA;u9^Rcc%KxvjD0jB{}p>V!q^S=rOSH69nrey;U}fXtG1gk zc8b|hW3;O`N$j+5GbjUgaklks2AF{+$@z+Fh?AHoekbF?7!wEX!-&g?&6vdA?DsZS z`(ae(d@J13>hYUDlS6*C%6l0#enYN%%E|r=dQ6@7drI*?7IbX2>?yw0^@yUbKckI& z&(|?_bFVA@)3w$q=zhg_6l<;eNV9L2h%M5*SNy8D!md~3yMkdj=(Vk8Pw^XeysG_S z_Nz+ZOjg4hbRYW;+HMVdN%pHt%*)i6t8(b8&c37aN3pR_D~bMQ{TVc!W##AcRTMZq;+aB{#nFoN?(AIOzNf!& zVM+Nr@~eu)Rf(d}1v%ciN+ckSPW*oQD*Uc_Cw^ajUU5Qnjtemhzl+{kAXyNdIU&}; z@2HcUir-o9G)UqPC_{GOciKAzBy5-xHYy}+neBombF!^VL+YiR%2Jf{>qt3E%6U@K zufUTZz#)myOxcb2%vV{;aUE`@QLbBHr;gvsckN;JX4=(YoNb{#VgD;kyFNb;EZBnCpn|3VeSM zucGe=NTC=g_56+i%5jysI*f2^KrM%B%eN5`9zYk&8O;wB7%Jk``)c{bc*-5OaqB)uVI#DBfbAqo z7De}4vt2EI*rPSCX~V^sbTxl&`{0-Ioc5Y$jCMX@Gvmi@mARHb)3C!~r-ywyhsLV- zam0z0eb@|o-E}N!E5A;4z~^htRnByu=%f9v;|-(j`aA$G%J*!L_|*8N{d|!2tJoS< zeb=f>Y;=wWx)!Qm*bY1Ib?j-?zg<17*ojNkC)&Qx6SC=|Jxb=abu4}9GjBl|>~ z&QmwogBn})8~~T?EN~ee@2a6 z#y{F3>u>0>%lIR|?la$vhua~^JnW{}mo7u{>4;Ot5)y_n*rl_rhy7T|kbKuGe%i|! z6!X5$D&DrP&49{4ljXmoeOJ$zRo}c0s;-slF^Rub?tiTI2E{&7ubhj)`cv$!_WnoB z1ByM>!t0-+9BkG2mHk(jGeGgL=44d$5ygMH9yy@+k1A%>Pg?QciqF3*-WOv1Gg0S& z!oRvMQPi!T1NF5~{C&*5DiMD+N)Fg}ls+j&_NzfJ@UZ`= z{84P|(@tVcvwr?%jAFBkTh(LS07~fRF#C+c7sbT>oJfoheIGE62;#4q_l!lAz!mEC zfPIA=zwLR?{_P}i(OEDW7C!bHmVJrc(Vk~4x`d6;IAC8P#ZT+zD|Q86C>$vEgFb5m zi(SD;90YnED7J%)pD+fp?u<{BupQWSf$hR(Q1N>@=NX4n3A_U+V}5b7KfiV)7FXAI z_U%=|C+!Dcxxl`C#!nbR%Xh|TC*hmkGkH1}*rvz$$!C&n)LQ~eA4}{mB5@rF=Py*kmXp}ma0RY#t?TjC&gpJ zlK7ScQ7m*qjN|(u1Y|CO?N*=CuOZI*^?OG$Ipg6~2w+bp|eyIaa$ zDf^`yDoKjB@+JErO8A^gqY{sb&8+7nK9+@2yNjjdqnoAVsaNvWFZmo|Njw|)0tn(A z|6{pGA|9-Zfn}m~GO%p4ZU!wQ*3n#6tgB&$J)STy46lTNf9QK6K8f;elzM(o1l*Tj zl=mouD8ndOlst;LZ_0yQR4|v%q>OG)+9n_5YXGIkh3Qa-0!tEwc%bvZjNeYW0RkIj zm$;~E)1sf|^ttgB$RYISQ8XxYzEv3!j+(qPrjYSuU_Z&es>zUX)Z~NxGe&L)B=fLC zT+nlZs`u*(k40)lm$@hBuwGlI3h?A_}n1YxeGhT?3 zOb~JTwGlI3=)SN0zH#z7LY%apGfd_sMfvM@4+JqkPWwo~(teJ)I%h@{Uh00vC|HU? z6ziAli`M0{Rt3@{E= zf|q=~xM3Xi@=vid23?{rT`ydWrC$CicE(^O(O;LGVJvmx#Wq(ITy;5T6#rh&20!yG z&lSeR5^I)?dfgP?e&X{oH_N_jNpM2T3 zjG3r#GOnonQEcqft;F0Ol+h@#GPWpu$(VfoJP9@;;U+>}Xq|m=qF^O%LOq8SUtX>$ zPUlbzfpDVWrE64ZdrI-+G)A}M2z!$K1SyH@u()BIuaR*^bVoI{jdP(3I zK*{@wiLv{!Au$ZFt++_wqP^vbACAEzFXS`LHsUIQk@9E6O^h6in>o@)?DB(Sv6H|} z=g*+TUxc(RY@^y3*++bn%_Prhbct9}GNue<7A22ja>cQ6TlUeI3<~j(;xSQ3e05m? zUyerKlZG!tTP6Beh^|t7h3GGs-(3fNLG1I&9pwx3h>J%62 zsZKA#PqEXB>T}InseX=9r#R?^G4#EF<_mR`b84V2*Mh!Uj=p{lT*;R*-kc}L`>q?X zK$z{~)%7_!o?=()1*xA2sn*A(elDb1=XE>>dbLh*5+0H=A55;)da*R7lg3akVSFiX zb7sBU)ECTpiBCAjn0`}_e^4)R3!{Db{y$R>Lw)#a#!|h2I(=;;@xKD~Az5D~`tWsu zC4I5b*ITO7_x~M$auZ5izW)zU+OG&0pfekV?yU?Ix}MQ_K8iwV>gxp?Jt$? zF}lSC+mw%fc+|0FVJ|kk0t@XQ+Dkf4M{PHJ!p8uLtMzh$%l5l*++g%N7C4E=ybHXR z{5tgyKXiUr+6Yijc@gQ^3V)O%zIkL0obZXU86F>jq}Xsf7VZl^=={$H9ZxOP@E0YQ zMHwG)8d{#hHtp?D&aG;EO1~Uq)N-^MHZOg&MkpDS&|ph(><59!st{NC6~mljte?EVWCp@7i$!H6&L(vqwwj` zN6q_t*iC!1t*@Quw#H{&&K##4s95znY}D<{RPn30RDB%lDptJ?ys_9@Dy}$ksA00a z#$(KYHqP^Y^=qd|_{{s|A*lI{#HWgt>)@g8*C2H*7#B%ZAIG|imFvJ6o7^PM7#GPn za;RXMbiahYSmkDe@j2;!9{B6_K1f;Tifdm3_8paB)?be`9`igrcU>L3w zj;b6r2%lDaJ(lm~8idcJhBKCXo%V}{vuZBaX@6Percoy>HTj7-pR}hd&P~D#UAE1q zr{~lI>UwiMu3Qo>#z-<|I1LpW*MTt=Gn^G?0OKSXBb<}YOvVX}=@{Xx;F54L&KU7& z%caC5kv5+-`5XmDeOCPGI*g0$wVl>k#aoAUr~4s8#Zaq`@x=Y4 zb1N=76@P@7^6)i?GU?t@#bZ^E5TmCxQ!$5#mGY4GDP2n(?Q0cRyWVD?%|L515aOBS z>AckOsH>v)#jQGcWVA{r)|e{h5_~ zri03*Fzhr?ewx3F1P)SR@DP;E2I6y&u?7`;7U);_kIwN~jjA|Wy={BFPt|8`oB9%kE4nFg7;#9>K zCtMOsQpIOg$GBMKsERRWd{+IOh=w~hIjZ4`%lS#?W>Upgt&Xt+amfW0XIwA=hoRzf zQpcQZ9Pq1H=@Np!uAFGd6dAfJ!%Zq>hMkXQlZ|Ts{Y|C%=c0T^{c21h@@c4k;!sDdwEt<11I-~dwxDfGymi~(YHW6OV5Db#`JiG~{RCxG;!CTx z<6Z}5Is<(3s>T@rpL8Y}D6u#v)!3ZYft`5ud>^3tfgP`B39T#PXjmRq9;l93=>6(2 zimU6EY6o-O@JW6$D6u{t)%d*DDMl39cUhFG9H~BPjg|Ij%~I;TR;yF|D6}7m;}I0% zc@(9nazSmWr+z9lp611dKmJ>QMf{OPsvE_k=q%M&n19i(RL_}zgt6ZogFj$bz+V-l zI{s>1F^9jDNA+>k4Iw(I4jnO(pgMH)hxVvWlK#-0K%Jyn9~Ag=c%?tsCs2nObmP@$ zx+&LltnV@1n12vZpiV;z^iO4-T(RDCQy@i8$)#DBE+^G{N-j-(W!+Rsy;{!+yi7%p z`S%M;^}dR_Q64jB{;kK-7_~mWS`V(wR{{Tm&vG3+m+Re19cxk64=Ht%O!O46M5ya) zU20eA%XYw3P<4Y073iOy)iGjy4N?{8pUQfUR0aA+<#15pbFkEHj#2C5%GxP)@JT6V zPEzaOwwxQ_R;`!w2RxT^3_MrV4QaC<`kbr}pq5>@ z@1`-dUr_Hi>p8PNWY%G*05fJClM66o)`6&isik@j^&webAqp5$(ie?FzgefhE15=t z<%avak~lZ%`9TVW&e04CovCzPFDgYQE&>eT1-o=D9YmpXK98dMQtQ;0MImf`0l2Qa z3B*Bs)AcHYLh%)Al^{UF2l*w=9r?7uM01Bxl0jgK$_DT<=ag?X`PRqy!6|)H{_11*20Lfvr7vncF8moPR=;)lt;-&- zh8vi&F8J`A13r8ttKoOj2j6wQw%K6>uCxzcqFRfL9eyYJdUH?w+TlcBz5)2BbxzvF zP)(0{bKG?vs$bc{5R6s!+B!paags%L+oPHaRTFTwe^Z=UsjJpZv!_yQ;6(+ zty7*XaAuv(jnTN8*8pIRWn32g^PX-fsmGzV!Tn&2a;1V-6(`l>inA6jJq~VTUM$ZY zj)!rwWPPJa#;;4n5$k(t3%nKkqfypDkAvF4o{Bqu9e3RE+TqjVko;%u#>YjkT`~h<)x;j25^X@%))||B2XQpZiV3f5rPR#MMW!H9~2U{5RA%z1j2`&%6^w=s^! zR*f}|{fwVwj4)C@CmB~Q86(`TazKwO2650ipVD#FYR3S#E8ZEe9vAEDAT#jkns>%u z2fUv6)^Rd^mU@4P?JR5$qYR)}-cvHBwrn%dW}wYLn}Ie1b_|4goeJbSYllyd$8KC( zAI^KbHvi%9>NH=Qi2wHUhx$E#A@1q@M5t$V81|ba|B3rd82p57vq|_T?%O|qso(x5 z?o(msY8dvLB>#!~_Rrtzm;dJTJFG!Ij2sQqW)tLp`SZ8nso}1{O~8NoXT#t>9cHeE zVY>EiAHO)x`WTk2l#bh~P3si*eA02p9WQaJW43E!Z0z@d zj=cf+PwAMewGmIO-;e3oW6uAi_ggxCy^VO1D6#$7um#?*&TPMSiNmCh*GU`m22uR_ z4`sfNKdv~5LmjJ|Hs)vJ7@HnL95E7iI!?E3*ofshq{k6M{ym>N7O@S)Ej|m3p`3F* z;vUd3`e_4);R8$H+~~2y2^(>x;|rn&SoP+;hw%5qDj# z$tnhkBk+4H2WuMIwN{U>S{pbczcQTdfI2KwGg zkHu>n*yveBHVQ6uj8XWMu_bliaccwTAWEovL613P{wWq;&oOnpVYh*o_QFV5yy*DD z@*`tN>i!y~4g4vTz|SN-{t);l%qiVZqqc)y2c3ri`EM%TEqLk!R~f&2S=cT5v`Luy zbi2*kMy&K4DUIUk8lm$7J6`xF--IEO=QcX#xU?yL$phtJ9;GIiI(8>>q;2xi@|^4Z zscrB>>p|CT1BLj?qR>5Hd0o`;R9E}tSeip~$p+bqWsVo|T9GIgbAoEcqS0AgB}6Y( zCq!p)g%Dj-A4h#$;2*ql5Cw!xby69q57BVcQ5F5Lh&s%eh6-Horm3P6u z2l%&C?<6y#6Rz+tnUVDbc^91p{39ysr0UG!9{?i3bwjD6TGlId6QLY$qOz!)2&G=F zmk{R2no#QnQpoXYJ-1q)u&W!Z$8_4%yA*vl4VS)pZR-7s-dvp0_=Hj~Em}$6t?2ue zIxV%km*Ur~GACi^4KiBdJmI9CTRpxs%i`a{>M>nvU0LgHwXWnwuUc2~s$Z=uxi@4I zUBW;+ieoXi2(Q=b+{PXxK<0St&2k-EiU65<)Cq)H?=`n(sosx8WIe5?Q5u8&U)q~F zw5eVw>CLSVLu99y^oW4!{Zxk`s+(0h6Yzhjj=j6LqZU%=MDo zObB71hy?wgq+0tGLk4jDO2!aYLqZ}hNX8?sT5tclP zjz?`%e-?%C)4X zbS@x%l~>2>t#cmOLTiR-eNZwep0VjzE!vb*Db01$HW913=0WTw^HwlO!X(CFi?s>_ zuZ`U-^i7)wh^-GLl)V)2+S557=6WFg` z;@&fk0OwNv4Y-XVPUC@});%8}C${V2H*R30vndj+F8LVt8(Y-#5nn25F8#y^to1(c zZSz{@N)|Hq*8^Zoq7c?v@dV!Qb^U>r);@4NA=n&+&DiDuuo)r5?08+gtUvHJ%Gxsz zvC9E)7>nzs+w}+TR6X<6hwrT0Si>5R80xy`RSw<6hG7cak2ix9{G+H(8AXH+@+5Oe3v5VYQ17ee|qH=c*aogLWLa(SxgoTl*U8-z@L!XdPtSxUPxL&oVfTs6`Zw%( z=5Z}N{agsX8vg#+X8kiBd2G~u;QCn*_%WJ+Bb)WlHbZPKV#r(ZV5fuKhQ8*jyjaDE z{)WD$sqs0jTk&A0gU!5CyKx_96=U2nhk?(E2P++XH^sTn9NNU#sMm4NxUG25b>J+? zyfur|W2APo#O5XrD;{(m>^0qeksc$p-SE~dW36~lb%-<7EU~I_s4;R~?QfMj$8IOV zL(|;zscUK#uiD=#bt@iN2M*HB$^*x!%4UtVN?cXn|6^WL?tS7zU3acm;mPy;b!&X-y(I(pZ_8DrvD6#y6#+Wku_JZWtP7W z0vzX7uk7<0q*0)wnR$SGZ6O(?r#s{+Q%t1`z3U$5p7&&%T>SJQmroB%0=)TT* zHCDAAqc|hW0X2TE$ApJ5;K-uTdZ$s6jIS-*473?&Gtg$B%|M%hHUn)2+6)B20PZm( zyYG&RFDu^Sf-OoM#6y(+;^M2l{-r(NUjNmZ5SQGH5=VRe+v|^fqvuU6bq}z5)mPVA zjRRVDx&Pbi42Uv6ScL5>WpZ{yk{cp(gKh@s)(>= zpQQDt7uKJC?%Xo>0Hvra|81rCX}#6x(H?1~_|@3C-b(S)dUM<)(KcEsel>Qkw^IDH z-W)ecv{S7VzZyH&TPc28Z;qQKbS-laz%i?`h1S2~+Rq#y{+781;Fwj}LhD~~?Pm@U zf4WsT;Fwj}DzVc#t8sH3`u6w#wZ?nk9$s@@A_rRH8lcBb?H1YpmS;cnL1#lN@WAn^ zvK8W{^;Kim>*#NP|9A6V1L*yo?b*+{fH4`ArhXR8v8uA!W2SXgia;On45Y1(^fH8(;)})&3Xn{#oes2 z(mGnjTT6#|O?w7d#oom0pSFtEiH>ro8F{gay&2=A^|OlAsSfi~&B%*Y{LL6At)Eq_ zZgjAbZ8mPK;%~MXY2B>ibgP4%WV3N&#X+;hNb6=5rxzV;8O_9x6$i}}AFY>FjDB>m z*);cpRy;J(`un=)v*Lzy;HPQs1+8LiqV-Q%#TSGQ95uytz>13|S%1SSwjg!jX|&O~ zvf`uBW1@AjiYsUx{3KE6XUWAzEmoW~YAg~rtw+)-t}t}))zJ3>R-82W`lqa73R4HZ z(v8ZQ6)%k%3$24yJW=T2JJX1~S#i^daS;Dju|%nB#2m2VrvcZ$?rYCHg1d(L83E%~ zWrMAMAkX&I`2Ezu9er-NVcr*7#o9pYPdwNB9IsW3Vd`WKG{iYz#Y2OvKk*y8wO9EB zzS!Rrs(88H0P7!{>pgR3C*z(2R=8un{tdJC%q{qg?ehV~sLB|xe_F-aNOf>Z&k1Aq ztk{af7_L9@ma@XyNIJxjM2XdV11tVww*JId(h6sz>JX0+7mlqsj0-m6$qHXH=nxzI zZehOOTw8HjZ%mSxlohsS(;?Qno&{EX)_MI6D@@I-1K)ICP?zULR=n0}{VD#W6`oen zA^sFfq}K#1ZX;fQiZ^A2qg8d_BOO&9SaBTD`qR3nt^BujB?e?3(79L)t5|UzmM>|S z;z?WiZR@NV0AKW8A%hb5^CK&d@Dp^t5pKfo_Dn-Y637}^aT=6u ziIecg`sZ|39K>BmoT6|pcs>`b;)GqVxkWfAH`{A&MQn&VaGpXjB>#@{!3q*=I9-2U zZ}OY6venkrhXJfdlGX%;)@B$bD@(=NTA{1>4_-qUPqJ$j{c8d+YuxTFmVk7ba0CG|fp8lU znn1cttqCwkcDOC$6kX;h!chXkZ7@Q^MOOj;;MG|y0qH7Gi`pf}T?J}UfF(y=IkSm& z$*_I9U=Cc}Uz%v&Hnj0(Q>-4ZwIy%me!VSuEBEVd$y>Q!Z!>SXlHY{Z?krT|snupb zcCuUg;eNf{YgGE{uhAaT`b(>g_0E}g5?Z@cYj5D)RYRazojy z*f%c^1}RGO&Fhig&aLhTTt44TZ7;P=0Hyi;)HWF`wTGx}GFWO;vjo6wo9;^tm~GR1 zu~>jZvn?-|0g?|t(YICb1KCM)OA++;nlH?vQ0nnD>w89{NfCgnA%7cam$fRb^G7vsy=E$S3I z*&L`>T#f7(y!D|_9%WIg@m#YnN*;if&LujN#{A-TFrR#ouVj=lHkvQ+L%Bpe1Xcy&WVEh=J|_>kbed=@K4u?Y#j25G5F;Hb{hGd;+$gHP`L%}>6$mpYZ)i$C$A7k zGv!o6V%_#YoqX5rUJLQkwPB=Aar;_lHE(Lw5nsL$WAur+F4sd~rkon}iQQLj z`0-sUm$)B33_pGvd~T%AwZK&$N<;5q&YNlyG1A_leOtHukAd%GHI9PnL!6XT4bQa` zJ`iUspH;ktBx9^?bY8FbvP?N#Ffu_l`>-T>kW?B_ecLOIox`?f}2 z4F<3du5&%+J_EadN1P)ukJasLj(Fr zR^zQ!AL1Qoz?fa-vKgKk4~- z19)x9`*b~jtK?NiAKPx1pIc<@c&nxde@%T)rRIkn4;YhG$F=L_oTBTH>(6CjPD|Za zRLQHRzX$NMSL}GR(!&our^@^B*JyLCcpzP)#St}LJKn7Hz}7O?KVAnb9!SUiZCP>s zqidj9?0D1l7*Fs2%JW*KuJgd{R*BzhoXk@#3Hw>EJ{ozR9WRX>Qxw?kcvJPjk&XgO z9DS%fsP#BvjT0u7E2}!Nw%onCx;|E%kWL-bu1~%{vg3{Q;LK=c-k1knwlXJ9V%Pan z+n7&hRZBmoQTb5oPPlF1F;zd)0A5=9tfIV7b4jhYaLzb+CSdz>9PrliXH`3|Cf_$X z+)uVYcgFmdKdah#e;T=!D-%MZaO&^8+MG!iCS-SQ#I$3Dpyp{qsul|Wv;4z&vu)qGEe24B2M&t z;&z)aldmlEVI{#)s?Ae7uUzIp)$1SS8SJ(1tt{@@iD#>QC^@S**IceiGtBL?)Q;p8E7-mW}wYLn}Ie1Z3fy5v>9kK&}N{` zK#&aJJ!swD!#9F&JFXi6dmCeXbGw~a@m-rn-Z$;MYUfoL`PKR# zD^IyWIYmE5YUh;=2kpG_lpB;&nRZ^;bJxx*dv2=a4dqn(_axPq{%km1^geJ$LQAvgf8s-lT}PD*Y{gjuaPPPWic&&KdG*xV;Z6_o2=a zx7$2b@@*IzQZQbF`Wx${FURn#8zlo3|2gc09HDDd*LsXQ5R% zOSgGz=as`8NZaL+l^!*CURr!6Ndgjx0 z%+p!Le75?T#XRV;#m_54=fiDVo>}=;<R^=is^(`F z?6|6>2bc8Rp{4HO?Ko+%ybA1Iy$YYHR^qe@55*SZA!Wxykb3ahGS@>p-dZT9Lc9K1 zaSL8YTY+1v*h$w4vC|sc#UGX)KI#1-Jwz7A%^tU0+fd&B*>TEx_#S9>emPcM zHhbK(wtCD_w&B02@3ri>Z1TK{^q#5;$0N%zv}w7RRH@CdjLX%PC$1nOhBgFVC^)vcdC; z)+E;VQ##*z8~7Xg8H64Ww;MRGMrlnrjz*R;w?-OqE$n!2#8`lt)}SeJN?jMkG~8(G zq2gETjTQ%Sui|gEI^v_BRgX2wy4dmGDEKAU19ovVlOD0s^V88rSQ|Uu8v(oIxS{XQ z?f9>zN8GW$F52O5fV^tz>z>`Zupa9}pO=sAvk1ni%NXYsahcNbwqhGR(6fkGo?Uk9 z5!1XP4x8@%p4}Qa)q@kd@5;t$&Frwp38Un!f7!^y}>ntEcAoDetdYo6T#)zA}nD0Ry(J9xlUk6foc*3Kup zMe(3l@{7*?$kx;jYLpn@i(+o)ligwjqzA`olq^c<`DF(=3g0ruv>k@FJ{$(XdlH4- zTho3HCCBWLhviSkLNO)ne7E({F@SZTXB2_wm|X<$6=WXMz98)FX9aeI$3Rc(gEEMc zMe()%cH#K7E3pyAb}reG9CJO^E`>sCID$fFgX3JY3(buU>63gDhLoMnw!ZNhp!G$e z`+ox^gOWuVMXAU+yGZqMaxU2*TV#`Lr|jl8yS`X@O^6{>4AEOGPKc5UEmS6i=v=K^ zAxKvMDMRIPIa6mq*UP$9XcUX|ujpbOra}jEWKjCZnYwY(3D%i)(*>NA#+j3f#T<2s z{*sQ&!Ja`v^p3?WD-amnFA%!E$js?bSX(u5E*Fiv8q(3PNw3SF+` zSGp2n3g#LPb){jVJQuWaUBaXh3>g%aUzR!ne*l0EBvX*F*0-k)o69$4!ppE>7PoI~f7w~tyW)2mdJs*Gr2^`(S6vCk)+r*!?* zIiTybmCh)eE>-9npVM@7FJPrBXybY@&PvCmGUk4aW3pH`1Rd8|LSd=nfaSOZjpN{? zu5^!NlmJRy>0Zi8m#Y{@=TPY`4Qe@;&Y|)>gVgn#IvI%6(b+`jj`@;;+U0wdk}grw zNzf%7-zOT%+%7q;py+y~uEc6-Zokx(z?5`DQdeTj)WNNZMY3h;;8yBP$EFT$rOtG0 z>UzsM(}}4YD(lRq`43cbt{ENa$OY?iXp^p6(UC#Y4JkT84ILpa&!r@U&Ya5jD3PEW zsu+hGv( z4>;r6|NQ;Y`5XUsqlaJn^|foJb~yhB^S9n|Ph;kTzj@EWdx%%XH~yZOHZ!qf$F}cD zexUECPyTND!i&@z6K^bLMlS-Os;#{o)Vrz3ib=?t1zQcTan~ z_kk|s@2}sv;gV|#S1(@v{L6#OhEMs*&z>2*Z1K`97SHLq^0HOi9ND|UAO3Rvf4}?8 zW>Zq>jV^!p$#YKK>G#8*9X|E2q3ho>V|dA&uH822n_?V$<{{rc;PCk`uh~Do)2y}r zoxWKFmo_^(;f8RuW=Zfz=l=|a0`VX4>KksaK z%vtN4zvtfh`Tw2qgQee@^YtmIeQx~K$jDm@zWmR(x4+_)556>K>x=K5lfLNjj>O=i zrCa>_mUn;i#=yWDV|zR?d+UquS(3c_jh%n=n;voY@0K0?ftU8b^*_6R?6U1XdjEM} zz4)b9o_hJX59}&VJnE_C8!Wkf#T9RV?KiXk{?B8+cEgg-4DOprZ`+Yx`$OlhJ9WvN zyWUv3#kBL*-tV051MvpY{a_xssHw;M=oc7CA@n~z;~-VsxbzdUu{0YCqF z*NOXYz1HpXKQ{gH{Pv$dcg^(eE3?1)=dV|7b-qYu4veR0r&nIHLF_mYLP zHy0=W_#gLQHL>GAzPTj1*PUOz;xK`rez@eHpLu=j8Ar^RzvY^j-M47=Pp9;4TD;`X ze?5PfgU&qU+E4Ag&oO&ny2l#n-);NWf1lZH!AZ%Brf<7a^c}lm=%Q^W20#3{^*h!Z zeQw9kKbg4ndkCWA{eC!j~cdy#_k*^oN@a+W)Uz}q+{J!4H zdw+P-XL>%l{F-mi5m!x2JNSxCdiE9DU9({6KHa;0Y~YKJKDb#&Zeq9VPr7T-K_?x( z=E4HVdEn^RKe+u4+gv+y#?IoA_bvP1v9F0O5C7%^`)+umxa!EKf3tkImv)~u=fYp^b_AS#|4Xl5KXTGJ`HvU26dUdEmRRuof2V$6>%`+{ z6z84v%C7mf518L^#PjEkOg9caYvHl~SaQ@0iS%2$UOkc9XZPM?r(8SLc8mZ@g(rvtT>#TL}JS1`0|9kh` z!oj=GKl!)QHz&IH+P`o4+|KXb*LU)gldtO%H}t2!`M)<9mrO70f8yJT!JJQR#e{#zoc6#a%tom#7PkA@-w+p^>`>8*=T>NI)2QEM59}j&Y z@#mMGe&n9(Po1AQ?4r|`Ck}i6P1oO$`+XvP&AQkAx9jPRPXB$+D?jR3^jg=7uf6&7 z$LEMg?)%>3D>^4Oob}E+yZ!n=@zi6Rb^dwTXHWjgsp6{JE_~&x4|fgz?3Js&^5B7q z^kcJs`k$_EJhSva3p&2Cxv|zAXdJ!h%00)s*Acg`N?rQ-`H8R0q1qW#>o9KV@44~( z7oOi0)XuzTW#>^F%{*(X!sFA81w+XbyB_`h{hi6JFTUf?%e!{_-qrWq`@P-gh^JPi zX1?`-Z?k%&Vl0AcNgCM%Eyj9eX6n3VYzo#r4D=eotZQK z`s0obFTSlmx&7IP-@SZIam`Tb+S4BU`)ydGHJ3iq)$`+LZvRr@_UQ!U7q^{!#zEro ziOy&5I(40|&M=m4aly0y?CrVlkw^aZ^SkDY%$75z8fS0(!s~a5MH5|5{N!s}jjSzx zHMP@Htk%j>Lh+b!bmm7T|*we2td zv_|gc^B0Q2u}7aiVgvEPcb*f42VeZ}4%p;xFa6p{@2>I0c6}$G^RH9a=v&vg;*x8M z<2#Fki=D@xz0I$W>=MgYnYDuw>F-Rxb5++9+aEV;t&*yXK{io5PVrpI{m$&(+rT}%^;jyz&Y>iMtE6mx&G_yuvs7}d_$`m+hK;D7gi zQy32{IZNC(@8dI4*WmJT?S-%RzIe+s7u}th_TnuMUIYXSKKAr~U&@JfPy4{D9aC2R z`-U!K31E;b*Z zb?C9vL}BpM9lt&M@aK=&Wd0u?%wP8E(Mx&|)5afsBYVrzEoN={?!c6za*w7`%P+X> z_Sx?~w(Qwsk9gk~@|}PEXZQRYmi=a|&-mgcn_lw9odSzq{@TCH&pUg{0m3-) z>cyEG54q}|b6;O?m&|D$h0%kb*!oY)&RcK(p0g4w4%&LHR}cT#nxC1y<>7PRW88nq z0Uuj)&$}`&&VKXqPo+My_UymEaLj9eeE#w$_f4%7sjIfy_x#6yciEzKj2kZL>U#X5 zJC>ZV=#8G6It!WGdpAt|_<|*;t$D%W^EO*w^bCJ`{-%!~GVSqB@$CLPbbWlyh3oD8 z`A;7^1s#v=w&=|FUezO>{p`0=r#*6hk4P<@y5!Xl9(Vu8ADZBPR&u;hIWk-Mg#qRsh?H5Z#@`!`J_3DhJ>j>alI`y)X z);WIq1M3)P{`i-P!e0lTU+>#*z4ig|$euHQ-zA)PVvEzUUp%iD=-|9bEmC%$uB-<65f&QBef_*;i@jJWhW zKcAm_Sft-CW*__d%8l=v_di#Wa&#nn$L8XJLpPcxmfyK_i-#Zm>WMJ3*@erei6>q= z=#Adf=8JXbi%TzicXRRFeoud3#^R$NKWyqNGmM8$d29AH3savuaG9}Sj@WzUmyS=0 zr~j7ixb(nBbNAfvAl7~$d*+J|tZ(eGZO>l6-D~Hhc;6rXa@~&apSJkf19rZ(;*3swh?Cxo+7rm@u&Bkkrl~L9e#k=fBqUj*>c(*H(oCK#x}^^mF&41SCq33 zz4U@)&vzf|$SvAr+8&GlAo|vR@a3&~*E4oG{2;OasXHEb{4-yBX6mC)&w6RDcw|Ax zv4{6>b?py_|IquH?dOOqJ5n=e-8RK|ZQsN8J^EMqLxvAt3%cL0{1YyYkDUML^c1$~ zXHQ)F;V->P64qm?5Ay2Py@UNGi|M+doUf5&v zlk=U%soj@7CoWwKB0u!0M;2Xu{$YFl?XcH-#KU{Or{jm;e`l7s^~{g%^Y#A3<2%mY z;}<&}Hbk03zOw9{CF0*V7WO=Hboja*@B8lU^DqC?p<=IBW_;t3YrE1{U-_Zp`l&}> znbFg6)XHbh7B4=1-67}gGd(F5&!09pnmXg)sf%Cu#7$lA5l{U42MZ6)KbPC~r!$gM zo_OtNS3P#b#Db~oLI3UCZDU_6tZ`-HrMnlrdX)CZ>Q_J!Lw+$aBB>cjsy=d?Ws z6Up~pYlKAi4#sYPVQKB_{DRQ2j;hb_u@il>e-(RO#AfuKY8t&-$;LD>B@b+JH`0f z^o8Be&iUshH$Qyk1HWE){)zVweiC>t-uS{DuYa|Bm#fb>Y~+!emR(+0Vh za^wH``4=zVN-REMmwT^zY~F=m+3m;&F1+F37azN3hj&k0_ZacyBinEC=&ry1^dWoi z-gnsPBcl^vUH4S6*VfmZzfbmqFKzILo@?Kk)%A}ZU;N zEPVWx`78f)*)&r(``#xX8(+BfgZKR77jKSix}Nyh=dL-o=e{ZXUA6m%-t3$>`>;DqIy)lY`*jN zC-(o$=EjW=owEH7JG}3@i>4m<=7UFU_F!`SJ>us}w>W9`zUS;Rw0PF3znWLvYD(^X zADOyv6xqCViw`Y1YpcB{=52Z3NhdE|-ZLflgGbg{zW@HupFaE7uOStd>~zKlublGz zxi_Ei)*<;D{=Hzm!6UKHa3y;8=6^3*_VgQrKYIB7jSj^dq(40L&+pwNKEG_)2R=9d zJ>Opcvpa3P+ZvC5@vfu3`rb2-`D5bPgWkPy(GfGY`RIc4?pQK!*YQoia$I6yp9Ra0 z`PP^B&Y$?Pm3OWA`a?aNe{TJ2Z<+p$M_x{EKkZ{;@doq%_^YwKuD$w}d;h)lg0+)> z-eC7vK7IYmKkZoS{7X9*J#~Kas$1@Tc668F^TpsMi~BCzM4YhZ)CaFyJhsWFCqBR3 z))yRb)dv4P_=%ere|P4{nO~T0T=VvI`yTMk*T3FJYxGN7Y_b)#b-YG<#S&dy5q3xzVrJPe}6KY zS-Je{zxn9PdyoF&qy5{hdV8Dy%|BC2?7rgYi#jiuvn}0{?EQ)T_PBkw^m+duBh`VC literal 0 HcmV?d00001 diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Library.wxs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Library.wxs new file mode 100644 index 0000000..3414644 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Library.wxs @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + NOT Installed + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Microsoft.InnerEye.Listener.Wix.wixproj b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Microsoft.InnerEye.Listener.Wix.wixproj new file mode 100644 index 0000000..80366ae --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Microsoft.InnerEye.Listener.Wix.wixproj @@ -0,0 +1,91 @@ + + + + Debug + x64 + 3.10 + 10b27116-9b19-4f19-83c0-19af5aeaf6d3 + 2.0 + Microsoft.InnerEye.Gateway + Package + ..\Microsoft.InnerEye.Listener.Processor\bin\$(Platform)\$(Configuration)\net462 + ..\Microsoft.InnerEye.Listener.Receiver\bin\$(Platform)\$(Configuration)\net462 + Microsoft InnerEye Gateway + Microsoft.InnerEye.Listener.Processor.exe + Microsoft.InnerEye.Listener.Receiver.exe + SERVICE1BINFOLDER=$(SERVICE1BINFOLDER);SERVICE2BINFOLDER=$(SERVICE2BINFOLDER);PRODUCTNAME=$(PRODUCTNAME);SERVICE1EXE=$(SERVICE1EXE);SERVICE2EXE=$(SERVICE2EXE) + false + + + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + True + + + bin\$(Platform)\$(Configuration)\ + obj\$(Platform)\$(Configuration)\ + + + + + + + + + + Microsoft.InnerEye.Listener.Processor + {163f75d2-4e40-4f9e-a671-ab3ef0e566eb} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + Microsoft.InnerEye.Listener.Receiver + {67e9b1c1-af93-46f7-a3f4-70ebab5e34bd} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + Microsoft.InnerEye.Listener.Wix.Actions + {001c5475-43fa-4348-b218-8f12771b56ee} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + + + $(WixExtDir)\WixUtilExtension.dll + WixUtilExtension + + + $(WixExtDir)\WixNetFxExtension.dll + WixNetFxExtension + + + $(WixExtDir)\WixNetFxExtension.dll + WixNetFxExtension + + + $(WixExtDir)\WixUIExtension.dll + WixUIExtension + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Readme.md b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Readme.md new file mode 100644 index 0000000..4fc360b --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/Readme.md @@ -0,0 +1,13 @@ +### Debugging + +To debug the MSI, build the MSI in debug. This will notify to attach the debugger for every custom action. + +To get more verbose information for debugging, you can launch the MSI using: + > msiexec.exe /i [REPO FOLDER]\Microsoft.InnerEye.Listener\Microsoft.InnerEye.Listener.Wix\bin\x64\Debug\Microsoft.InnerEye.Gateway.msi /L*vx C:\TestLog.txt + + This will write information to C:\TestLog.txt + +### Important notes about the project setup + +- Do **NOT** remove the non-existant `Service1.Generated.wxs` & `Service2.Generated.wxs` files from this project. This file will be generated while building and is not checked in to git *on purpose*. The file has to be referenced in the project so that WiX picks it up during MSI building! +- The build process harvests the bin/ folder of both services using HEAT. WiX does not allow to exclude files in a convenient way, the only way is to run the resulting XML through a XSL filter (`transform.xsl`). This approach is used here to filter out all executables, as we need to reference the main executable in order to let the installer know what .exe to start when running the services -- referencing harvested files is not possible due to autogenerated IDs. \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/WixUI_FeatureTree2.wxs b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/WixUI_FeatureTree2.wxs new file mode 100644 index 0000000..3854b33 --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/WixUI_FeatureTree2.wxs @@ -0,0 +1,42 @@ + + + + + + + + + + + + 1 + 1 + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/filter.xsl b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/filter.xsl new file mode 100644 index 0000000..88d26bd --- /dev/null +++ b/Source/Microsoft.Gateway/Microsoft.InnerEye.Listener.Wix/filter.xsl @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/NuGet.config b/Source/Microsoft.Gateway/NuGet.config new file mode 100644 index 0000000..78b9039 --- /dev/null +++ b/Source/Microsoft.Gateway/NuGet.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough1.json b/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough1.json new file mode 100644 index 0000000..d9618c1 --- /dev/null +++ b/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough1.json @@ -0,0 +1,81 @@ +[ + { + "CallingAET": "RADIOMICS_APP", + "CalledAET": "PassThroughModel", + "AETConfig": { + "Config": { + "AETConfigType": "Model", + "ModelsConfig": [ + { + "ModelId": "PassThroughModel:3", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": -1, + "MaxChannelImages": -1 + } + ], + "TagReplacements": [ + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 8, + "Element": 4158 + }, + "Value": "NOT FOR CLINICAL USE" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 2 + }, + "Value": "InnerEye" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 4 + }, + "Value": "NOT FOR CLINICAL USE" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 8, + "Element": 112 + }, + "Value": "Microsoft Corporation" + }, + { + "Operation": "AppendIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 38 + }, + "Value": " NOT FOR CLINICAL USE" + } + ] + } + ] + }, + "Destination": { + "Title": "RADIOMICS_APP", + "Port": 104, + "Ip": "127.0.0.1" + }, + "ShouldReturnImage": false + } + } +] \ No newline at end of file diff --git a/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough2.json b/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough2.json new file mode 100644 index 0000000..19265bc --- /dev/null +++ b/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPassThrough2.json @@ -0,0 +1,40 @@ +[ + { + "CallingAET": "RADIOMICS_APP", + "CalledAET": "PassThroughModel", + "AETConfig": { + "Config": { + "AETConfigType": "Model", + "ModelsConfig": [ + { + "ModelId": "b033d049-0233-4068-bc0f-c64cec48e8fa", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": -1, + "MaxChannelImages": -1 + } + ], + "TagReplacements": [] + } + ] + }, + "Destination": { + "Title": "RADIOMICS_APP", + "Port": 104, + "Ip": "127.0.0.1" + }, + "ShouldReturnImage": false + } + } +] \ No newline at end of file diff --git a/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPelvis.json b/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPelvis.json new file mode 100644 index 0000000..788b266 --- /dev/null +++ b/Source/Microsoft.Gateway/SampleConfigurations/GatewayModelRulesConfig/GatewayModelRulesConfigPelvis.json @@ -0,0 +1,102 @@ +[ + { + "CallingAET": "Scanner", + "CalledAET": "RGPelvisCT", + "AETConfig": { + "Config": { + "AETConfigType": "Model", + "ModelsConfig": [ + { + "ModelId": "b033d049-0233-4068-bc0f-aaaabbbbcccc", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": -1, + "MaxChannelImages": -1 + } + ], + "TagReplacements": [ + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 8, + "Element": 4158 + }, + "Value": "NOT FOR CLINICAL USE" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 2 + }, + "Value": "InnerEye" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 4 + }, + "Value": "NOT FOR CLINICAL USE" + }, + { + "Operation": "UpdateIfExists", + "DicomTagIndex": { + "Group": 8, + "Element": 112 + }, + "Value": "Microsoft Corporation" + }, + { + "Operation": "AppendIfExists", + "DicomTagIndex": { + "Group": 12294, + "Element": 38 + }, + "Value": " NOT FOR CLINICAL USE" + } + ] + }, + { + "ModelId": "b033d049-0233-4068-bc0f-c64cec48e8fa", + "ChannelConstraints": [ + { + "ChannelID": "ct", + "ImageFilter": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "ChannelConstraints": { + "Constraints": [], + "Op": "And", + "discriminator": "GroupConstraint" + }, + "MinChannelImages": -1, + "MaxChannelImages": -1 + } + ], + "TagReplacements": [] + } + ] + }, + "Destination": { + "Title": "PACS", + "Port": 1105, + "Ip": "localhost" + }, + "ShouldReturnImage": false + } + } +] \ No newline at end of file diff --git a/Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json b/Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json new file mode 100644 index 0000000..c8e2482 --- /dev/null +++ b/Source/Microsoft.Gateway/SampleConfigurations/GatewayProcessorConfig.json @@ -0,0 +1,22 @@ +{ + "ServiceSettings": { + "RunAsConsole": true + }, + "ProcessorSettings": { + "LicenseKeyEnvVar": "INNEREYE_INFERENCE_PRD_KEY", + "InferenceUri": "https://innereyeinferenceprd.azurewebsites.net" + }, + "DequeueServiceConfig": { + "MaximumQueueMessageAgeSeconds": 100, + "DeadLetterMoveFrequencySeconds": 1 + }, + "DownloadServiceConfig": { + "DownloadRetryTimespanInSeconds": 5, + "DownloadWaitTimeoutInSeconds": 3600 + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2020-05-31T20:14:51", + "ApplyConfigDateTime": "2020-05-31T20:14:51", + "ConfigurationRefreshDelaySeconds": 60 + } +} diff --git a/Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json b/Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json new file mode 100644 index 0000000..a15fa4f --- /dev/null +++ b/Source/Microsoft.Gateway/SampleConfigurations/GatewayReceiveConfig.json @@ -0,0 +1,24 @@ +{ + "ServiceSettings": { + "RunAsConsole": true + }, + "ReceiveServiceConfig": { + "GatewayDicomEndPoint": { + "Title": "GATEWAY", + "Port": 111, + "Ip": "localhost" + }, + "RootDicomFolder": "C:\\InnerEyeGateway\\", + "AcceptedSopClassesAndTransferSyntaxesUIDs": { + "1.2.840.10008.1.1": [ "1.2.840.10008.1.2.1", "1.2.840.10008.1.2" ], + "1.2.840.10008.5.1.4.1.1.481.3": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1" ], + "1.2.840.10008.5.1.4.1.1.2": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.5" ], + "1.2.840.10008.5.1.4.1.1.4": [ "1.2.840.10008.1.2", "1.2.840.10008.1.2.1", "1.2.840.10008.1.2.4.57", "1.2.840.10008.1.2.4.70", "1.2.840.10008.1.2.4.80", "1.2.840.10008.1.2.5" ] + } + }, + "ConfigurationServiceConfig": { + "ConfigCreationDateTime": "2018-07-25T20:14:51.539351Z", + "ApplyConfigDateTime": "2018-07-25T20:14:51.539351Z", + "ConfigurationRefreshDelaySeconds": 60 + } +} diff --git a/Source/Microsoft.Gateway/Scripts/ProcessorServiceInstaller.ps1 b/Source/Microsoft.Gateway/Scripts/ProcessorServiceInstaller.ps1 new file mode 100644 index 0000000..ed4389f --- /dev/null +++ b/Source/Microsoft.Gateway/Scripts/ProcessorServiceInstaller.ps1 @@ -0,0 +1,9 @@ +Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Server -All + +Stop-Service -DisplayName "ProcessorService" + +C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe Source\Microsoft.InnerEye.Listener\Microsoft.InnerEye.Listener.Processor\bin\x64\Release\Microsoft.InnerEye.Listener.Processor.exe /u + +C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe Source\Microsoft.InnerEye.Listener\Microsoft.InnerEye.Listener.Processor\bin\x64\Release\Microsoft.InnerEye.Listener.Processor.exe + +Start-Service -DisplayName "ProcessorService" \ No newline at end of file diff --git a/Source/Microsoft.Gateway/Scripts/ReceiveServiceInstaller.ps1 b/Source/Microsoft.Gateway/Scripts/ReceiveServiceInstaller.ps1 new file mode 100644 index 0000000..5e35100 --- /dev/null +++ b/Source/Microsoft.Gateway/Scripts/ReceiveServiceInstaller.ps1 @@ -0,0 +1,9 @@ +Enable-WindowsOptionalFeature -Online -FeatureName MSMQ-Server -All + +Stop-Service -DisplayName "ReceiveService" + +C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe Source\Microsoft.InnerEye.Listener\Microsoft.InnerEye.Listener.Receiver\bin\x64\Release\Microsoft.InnerEye.Listener.Receiver.exe /u + +C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe Source\Microsoft.InnerEye.Listener\Microsoft.InnerEye.Listener.Receiver\bin\x64\Release\Microsoft.InnerEye.Listener.Receiver.exe + +Start-Service -DisplayName "ReceiveService" \ No newline at end of file diff --git a/Source/Microsoft.Gateway/download_dcmtk.ps1 b/Source/Microsoft.Gateway/download_dcmtk.ps1 new file mode 100644 index 0000000..35cf4d6 --- /dev/null +++ b/Source/Microsoft.Gateway/download_dcmtk.ps1 @@ -0,0 +1,6 @@ +# Set-ExecutionPolicy -ExecutionPolicy RemoteSigned +Invoke-WebRequest -Uri "https://dicom.offis.de/download/dcmtk/dcmtk365/bin/dcmtk-3.6.5-win64-dynamic.zip" -OutFile ".\dcmtk.zip" +Expand-Archive -Path ".\dcmtk.zip" -DestinationPath ".\" + +Invoke-WebRequest -Uri "https://www.dclunie.com/dicom3tools/workinprogress/winexe/dicom3tools_winexe_1.00.snapshot.20210306100017.zip" -OutFile ".\dicom3tools.zip" +Expand-Archive -Path ".\dicom3tools.zip" -DestinationPath ".\dicom3tools" diff --git a/THIRDPARTYNOTICES.md b/THIRDPARTYNOTICES.md new file mode 100644 index 0000000..51333bd --- /dev/null +++ b/THIRDPARTYNOTICES.md @@ -0,0 +1,3956 @@ +# NOTICES AND INFORMATION +Do Not Translate or Localize + +This software incorporates material from third parties. +Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, +or you may send a check or money order for US $5.00, including the product name, +the open source component name, platform, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +USA + +Notwithstanding any other terms, you may reverse engineer this software to the extent +required to debug changes to any libraries licensed under the GNU Lesser General Public License. + +--------------------------------------------------------- + +Microsoft.Data.Sqlite.Core 2.1.0 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.EntityFrameworkCore 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.EntityFrameworkCore.Abstractions 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.EntityFrameworkCore.Analyzers 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.EntityFrameworkCore.Relational 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.EntityFrameworkCore.Sqlite 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.EntityFrameworkCore.Sqlite.Core 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Caching.Abstractions 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Caching.Memory 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.Abstractions 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.Binder 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.DependencyInjection 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.DependencyInjection 2.2.0 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.DependencyInjection.Abstractions 2.2.0 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.DependencyInjection.Abstractions 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.Abstractions 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Options 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Primitives 2.1.1 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Remotion.Linq 2.2.0 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +Copyright (c) rubicon IT GmbH, www.rubicon.eu +ZCopyright (c) rubicon IT GmbH, www.rubicon.eu + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +SQLitePCLRaw.bundle_green 1.1.11 - Apache-2.0 + + + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +SQLitePCLRaw.core 1.1.11 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +Copyright 2014-2018 Zumero, LLC. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +SQLitePCLRaw.lib.e_sqlite3.linux 1.1.11 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +Copyright 2014-2018 Zumero, LLC + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +SQLitePCLRaw.lib.e_sqlite3.osx 1.1.11 - Apache-2.0 + + + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +SQLitePCLRaw.lib.e_sqlite3.v110_xp 1.1.11 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +Copyright 2014-2018 Zumero, LLC + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +SQLitePCLRaw.provider.e_sqlite3.net45 1.1.11 - Apache-2.0 + + +(c) 2008 VeriSign, Inc. +Copyright 2014-2018 Zumero, LLC. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +StyleCop.Analyzers 1.1.118 - Apache-2.0 + + +copyright company +(c) 2008 VeriSign, Inc. +Copyright (c) 2015 Dennis Fischer +Copyright (c) 2017 Marcos Lopez C. +Copyright 2014 Giovanni Bassi and Elemar Jr. +Copyright (c) Tunnel Vision Laboratories, LLC. +Copyright Tunnel Vision Laboratories, LLC 2015 +1Copyright Tunnel Vision Laboratories, LLC 2015 +GetCopyrightText copyrightText WithDocumentText +copyright tag should contain a non-empty company +1Copyright Tunnel Vision Laboratories, LLC 2015 DAn +Copyright 2015 Tunnel Vision Laboratories, LLC StyleCop DotNetAnalyzers Roslyn Diagnostic Analyzer + +Copyright (c) Tunnel Vision Laboratories, LLC. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +--- + +This project uses other open source projects, which are used under the terms +of the following license(s). + +.NET Compiler Platform ("Roslyn") + + Copyright Microsoft. + + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + these files except in compliance with the License. You may obtain a copy of the + License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for the + specific language governing permissions and limitations under the License. + +Code Cracker + + Copyright 2014 Giovanni Bassi and Elemar Jr. + + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + these files except in compliance with the License. You may obtain a copy of the + License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for the + specific language governing permissions and limitations under the License. + +LightJson + + Copyright (c) 2017 Marcos López C. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +StyleCop.Analyzers 1.1.0-beta006 - Apache-2.0 + + +copyright company +(c) 2008 VeriSign, Inc. +Copyright Tunnel Vision Laboratories, LLC 2015 +1Copyright Tunnel Vision Laboratories, LLC 2015 +GetCopyrightText copyrightText WithDocumentText +copyright tag should contain a non-empty company +1Copyright Tunnel Vision Laboratories, LLC 2015 DAn +Copyright Sam Harwell 2015 StyleCop DotNetAnalyzers Roslyn Diagnostic Analyzer + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Interactive.Async 3.1.1 - Apache-2.0 + + + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Bond.Core.CSharp 8.2.0 - MIT + + +(c) 2019 +(c) 2008 VeriSign, Inc. +Copyright (c) Microsoft. +(c) Microsoft Corporation. +Copyright (c) 2014 Microsoft + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.Agent.Intercept 2.4.0 - MIT + + +(c) 2008 VeriSign, Inc. +Copyright (c) by P.J. Plauger +Copyright 1995-2014 Microsoft Corp. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.DependencyCollector 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.EventCounterCollector 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.PerfCounterCollector 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.WindowsServer 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights.WorkerService 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.ApplicationInsights 2.14.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Namotion.Reflection 1.0.11 - MIT + + +(c) 2008 VeriSign, Inc. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Newtonsoft.Json 11.0.2 - MIT + + +(c) 2008 VeriSign, Inc. +Copyright James Newton-King 2008 +Copyright (c) 2007 James Newton-King +Copyright (c) James Newton-King 2008 + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Configuration.ConfigurationManager 4.7.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Memory 4.5.1 - MIT + + + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Memory 4.5.4 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 1991-2017 Unicode, Inc. +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.CompilerServices.Unsafe 4.5.1 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 1991-2017 Unicode, Inc. +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.CompilerServices.Unsafe 4.5.3 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 1991-2017 Unicode, Inc. +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.AccessControl 4.7.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Permissions 4.7.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ValueTuple 4.5.0 - MIT + + +(c) 2008 VeriSign, Inc. +(c) Microsoft Corporation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 1991-2017 Unicode, Inc. +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +fo-dicom 4.0.4 - MS-PL + + +(c) 2008 VeriSign, Inc. + +Microsoft Public License (Ms-PL) + +This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. + + 1. Definitions + + The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. + + 2. Grant of Rights + + (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + + (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + + 3. Conditions and Limitations + + (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + + (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. + + (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. + + (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. + + (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees, or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. + +--------------------------------------------------------- + +--------------------------------------------------------- + +fo-dicom.Desktop 4.0.4 - MS-PL + + +(c) 2008 VeriSign, Inc. +Copyright (c) 1998, Thomas G. Lane + +Microsoft Public License (Ms-PL) + +This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. + + 1. Definitions + + The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. A "contribution" is the original software, or any additions or changes to the software. A "contributor" is any person that distributes its contribution under this license. "Licensed patents" are a contributor's patent claims that read directly on its contribution. + + 2. Grant of Rights + + (A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + + (B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + + 3. Conditions and Limitations + + (A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + + (B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. + + (C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. + + (D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. + + (E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees, or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. + +--------------------------------------------------------- + diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 0000000..c873fa9 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,7 @@ +Please provide a detailed description of your PR here. + +Please use the checklist below before the submission. +- [ ] Ensure that your PR is small, and implements one specific change. +- [ ] Add unit tests for any functionality that was introduced or modified. +- [ ] Link the correct GitHub issue for tracking. +- [ ] When merging your PR, replace the default merge message with a description of your PR, and if needed a reason why that change was necessary. \ No newline at end of file