All files / tools/src/stateManagement/segmentation SegmentationStyle.ts

28.43% Statements 29/102
30% Branches 21/70
46.15% Functions 6/13
28.43% Lines 29/102

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452                                                                                                                                428x                                                             212x   212x       212x   212x                             212x       212x                                                                                           212x                                             212x                                                                                                         1032430x 1032430x 1032430x     1032430x 102064x             1032430x                                         1032430x 1032218x       1032218x 1032218x                           1032218x                                                       1032430x                   4022x                       194x 186x         194x                       1032430x   1031228x   1202x                                                                                                                                                           428x      
import type { SegmentationRepresentations } from '../../enums';
import getDefaultContourConfig from '../../tools/displayTools/Contour/contourConfig';
import getDefaultLabelmapConfig from '../../tools/displayTools/Labelmap/labelmapConfig';
import type { BaseContourStyle, ContourStyle } from '../../types/ContourTypes';
import type {
  BaseLabelmapStyle,
  LabelmapStyle,
} from '../../types/LabelmapTypes';
import type { SurfaceStyle } from '../../types/SurfaceTypes';
import * as Enums from '../../enums';
import { utilities } from '@cornerstonejs/core';
 
export type RepresentationStyle = LabelmapStyle | ContourStyle | SurfaceStyle;
 
export type BaseRepresentationStyle = BaseLabelmapStyle | BaseContourStyle;
 
interface SegmentationStyleConfig {
  global: {
    [key in SegmentationRepresentations]?: RepresentationStyle;
  };
  // per segmentation we follow the same active and inactive style properties
  // since the target (segmentation) is important not the activeness etc
  segmentations: {
    [segmentationId: string]: {
      [key in SegmentationRepresentations]?: {
        allSegments?: BaseRepresentationStyle;
        perSegment?: { [key: number]: BaseRepresentationStyle };
      };
    };
  };
  viewportsStyle: {
    [viewportId: string]: {
      renderInactiveSegmentations: boolean;
      representations: {
        [segmentationId: string]: {
          [key in SegmentationRepresentations]?: {
            allSegments?: BaseRepresentationStyle;
            perSegment?: {
              [key: number]: BaseRepresentationStyle;
            };
          };
        };
      };
    };
  };
}
 
/**
 * This class handles the configuration of segmentation styles. It supports
 * three representation types: labelmap, contour, and surface.
 *
 * The hierarchy of the configuration is as follows (each level falls back to the
 * next level if not specified):
 *
 * 1) Viewport-specific styles for a specific segmentationId and representation type (viewport 1 & segmentation 1 (contour))
 * 2) Viewport-specific styles for all of the segmentations of a specific representation type (viewport 1 & segmentation 1 (labelmap) || viewport 1 & segmentation 2 (labelmap) etc etc)
 * 3) Segmentation-specific styles (for all viewports) (segmentation 1 (labelmap) for all viewports)
 * 4) Global styles for a representation type (all viewports & all segmentations & labelmap)
 * 5) Default styles
 */
class SegmentationStyle {
  private config: SegmentationStyleConfig;
 
  constructor() {
    this.config = {
      global: {},
      segmentations: {},
      viewportsStyle: {},
    };
  }
 
  /**
   * Sets the style based on the provided specifiers.
   * The priority hierarchy is as follows:
   * 1) Viewport-specific styles for a specific segmentation and representation type
   * 2) Viewport-specific styles for all segmentations of a representation type
   * 3) Segmentation-specific styles for a representation type
   * 4) Global styles for a representation type
   * 5) Default styles
   * @param specifier - An object containing the specifications for the style.
   * @param specifier.type - The type of segmentation representation (required).
   * @param specifier.viewportId - Optional. The ID of the viewport.
   * @param specifier.segmentationId - Optional. The ID of the segmentation.
   * @param specifier.segmentIndex - Optional. The index of the specific segment to style.
   * @param styles - The styles to set.
   */
  setStyle(
    specifier: {
      type: SegmentationRepresentations;
      viewportId?: string;
      segmentationId?: string;
      segmentIndex?: number;
    },
    styles: RepresentationStyle
  ): void {
    const { viewportId, segmentationId, type, segmentIndex } = specifier;
 
    const currentStyles = this.getStyle(specifier);
 
    let updatedStyles: RepresentationStyle;
 
    if (!viewportId && !segmentationId) {
      // Global style setting
      updatedStyles = {
        ...currentStyles,
        ...styles,
      };
    } else E{
      // Per segmentation or per viewport style setting
      updatedStyles = this.copyActiveToInactiveIfNotProvided(
        {
          ...currentStyles,
          ...styles,
        },
        type
      );
    }
 
    Iif (!type) {
      throw new Error('Type is required to set a style');
    }
 
    Iif (viewportId) {
      // Viewport-specific styles
      if (!this.config.viewportsStyle[viewportId]) {
        this.config.viewportsStyle[viewportId] = {
          renderInactiveSegmentations: false,
          representations: {},
        };
      }
 
      const representations =
        this.config.viewportsStyle[viewportId].representations;
 
      if (segmentationId) {
        // Viewport-specific style for a specific segmentation
        if (!representations[segmentationId]) {
          representations[segmentationId] = {};
        }
        if (!representations[segmentationId][type]) {
          representations[segmentationId][type] = {};
        }
 
        const repConfig = representations[segmentationId][type];
 
        if (segmentIndex !== undefined) {
          // Style for a specific segment
          if (!repConfig.perSegment) {
            repConfig.perSegment = {};
          }
          repConfig.perSegment[segmentIndex] = updatedStyles;
        } else {
          // Style for all segments of the segmentation
          repConfig.allSegments = updatedStyles;
        }
      } else {
        // Viewport-specific style for all segmentations of a type
        const ALL_SEGMENTATIONS_KEY = '__allSegmentations__';
        if (!representations[ALL_SEGMENTATIONS_KEY]) {
          representations[ALL_SEGMENTATIONS_KEY] = {};
        }
        if (!representations[ALL_SEGMENTATIONS_KEY][type]) {
          representations[ALL_SEGMENTATIONS_KEY][type] = {};
        }
 
        representations[ALL_SEGMENTATIONS_KEY][type].allSegments =
          updatedStyles;
      }
    } else Iif (segmentationId) {
      // Segmentation-specific updatedStyles
      if (!this.config.segmentations[segmentationId]) {
        this.config.segmentations[segmentationId] = {};
      }
      if (!this.config.segmentations[segmentationId][type]) {
        this.config.segmentations[segmentationId][type] = {};
      }
 
      const segConfig = this.config.segmentations[segmentationId][type];
 
      if (segmentIndex !== undefined) {
        // Style for a specific segment
        if (!segConfig.perSegment) {
          segConfig.perSegment = {};
        }
        segConfig.perSegment[segmentIndex] = updatedStyles;
      } else {
        // Style for all segments of the segmentation
        segConfig.allSegments = updatedStyles;
      }
    } else {
      // Global style for the representation type
      this.config.global[type] = updatedStyles;
    }
  }
 
  /**
   * Copies active style properties to their inactive counterparts if not provided.
   * @param styles - The styles to process.
   * @param type - The type of segmentation representation.
   * @returns The processed styles with inactive properties set if not provided.
   */
  private copyActiveToInactiveIfNotProvided(
    styles: RepresentationStyle,
    type: SegmentationRepresentations
  ): RepresentationStyle {
    const processedStyles = { ...styles };
 
    if (type === Enums.SegmentationRepresentations.Labelmap) {
      const labelmapStyles = processedStyles as LabelmapStyle;
 
      labelmapStyles.renderOutlineInactive ??= labelmapStyles.renderOutline;
      labelmapStyles.outlineWidthInactive ??= labelmapStyles.outlineWidth;
      labelmapStyles.renderFillInactive ??= labelmapStyles.renderFill;
      labelmapStyles.fillAlphaInactive ??= labelmapStyles.fillAlpha;
      labelmapStyles.outlineOpacityInactive ??= labelmapStyles.outlineOpacity;
    } else if (type === Enums.SegmentationRepresentations.Contour) {
      const contourStyles = processedStyles as ContourStyle;
 
      contourStyles.outlineWidthInactive ??= contourStyles.outlineWidth;
      contourStyles.outlineOpacityInactive ??= contourStyles.outlineOpacity;
      contourStyles.outlineDashInactive ??= contourStyles.outlineDash;
      contourStyles.renderOutlineInactive ??= contourStyles.renderOutline;
      contourStyles.renderFillInactive ??= contourStyles.renderFill;
      contourStyles.fillAlphaInactive ??= contourStyles.fillAlpha;
    }
 
    return processedStyles;
  }
 
  /**
   * Gets the style for a segmentation based on the provided specifications.
   * @param specifier - An object containing the specifications for the segmentation style.
   * @param specifier.viewportId - The ID of the viewport.
   * @param specifier.segmentationId - The ID of the segmentation.
   * @param specifier.type - The type of segmentation representation.
   * @param specifier.segmentIndex - Optional. The index of the specific segment.
   * @returns An object containing the combined style and renderInactiveSegmentations flag for the viewport.
   */
  getStyle(specifier: {
    viewportId?: string;
    segmentationId?: string;
    type?: SegmentationRepresentations;
    segmentIndex?: number;
  }): RepresentationStyle {
    const { viewportId, segmentationId, type, segmentIndex } = specifier;
    let combinedStyle = this.getDefaultStyle(type);
    let renderInactiveSegmentations = false;
 
    // Apply global styles for the representation type
    if (this.config.global[type]) {
      combinedStyle = {
        ...combinedStyle,
        ...this.config.global[type],
      };
    }
 
    // Apply segmentation-specific styles
    Iif (this.config.segmentations[segmentationId]?.[type]) {
      combinedStyle = {
        ...combinedStyle,
        ...this.config.segmentations[segmentationId][type].allSegments,
      };
      if (
        segmentIndex !== undefined &&
        this.config.segmentations[segmentationId][type].perSegment?.[
          segmentIndex
        ]
      ) {
        combinedStyle = {
          ...combinedStyle,
          ...this.config.segmentations[segmentationId][type].perSegment[
            segmentIndex
          ],
        };
      }
    }
 
    // Apply viewport-specific styles and get renderInactiveSegmentations
    if (viewportId && this.config.viewportsStyle[viewportId]) {
      renderInactiveSegmentations =
        this.config.viewportsStyle[viewportId].renderInactiveSegmentations;
 
      // Apply viewport-specific styles for all segmentations of this representation type
      const allSegmentationsKey = '__allSegmentations__';
      Iif (
        this.config.viewportsStyle[viewportId].representations[
          allSegmentationsKey
        ]?.[type]
      ) {
        combinedStyle = {
          ...combinedStyle,
          ...this.config.viewportsStyle[viewportId].representations[
            allSegmentationsKey
          ][type].allSegments,
        };
      }
 
      // Apply viewport-specific styles for this specific segmentation
      Iif (
        segmentationId &&
        this.config.viewportsStyle[viewportId].representations[
          segmentationId
        ]?.[type]
      ) {
        combinedStyle = {
          ...combinedStyle,
          ...this.config.viewportsStyle[viewportId].representations[
            segmentationId
          ][type].allSegments,
        };
        if (
          segmentIndex !== undefined &&
          this.config.viewportsStyle[viewportId].representations[
            segmentationId
          ][type].perSegment?.[segmentIndex]
        ) {
          combinedStyle = {
            ...combinedStyle,
            ...this.config.viewportsStyle[viewportId].representations[
              segmentationId
            ][type].perSegment[segmentIndex],
          };
        }
      }
    }
 
    return combinedStyle;
  }
 
  /**
   * Retrieves the renderInactiveSegmentations flag for a specific viewport.
   *
   * @param viewportId - The ID of the viewport to check.
   * @returns A boolean indicating whether inactive segmentations should be rendered for the specified viewport.
   */
  getRenderInactiveSegmentations(viewportId: string): boolean {
    return this.config.viewportsStyle[viewportId]?.renderInactiveSegmentations;
  }
 
  /**
   * Sets the renderInactiveSegmentations flag for a specific viewport.
   * @param viewportId - The ID of the viewport.
   * @param renderInactiveSegmentations - Whether to render inactive segmentations.
   */
  setRenderInactiveSegmentations(
    viewportId: string,
    renderInactiveSegmentations: boolean
  ): void {
    if (!this.config.viewportsStyle[viewportId]) {
      this.config.viewportsStyle[viewportId] = {
        renderInactiveSegmentations: false,
        representations: {},
      };
    }
    this.config.viewportsStyle[viewportId].renderInactiveSegmentations =
      renderInactiveSegmentations;
  }
 
  /**
   * Gets the default style for a specific representation type.
   * @param type - The type of segmentation representation.
   * @returns The default style for the specified representation type.
   */
  private getDefaultStyle(
    type: SegmentationRepresentations
  ): RepresentationStyle {
    switch (type) {
      case Enums.SegmentationRepresentations.Labelmap:
        return getDefaultLabelmapConfig();
      case Enums.SegmentationRepresentations.Contour:
        return getDefaultContourConfig();
      case Enums.SegmentationRepresentations.Surface:
        return {}; // TODO: Implement default surface config when available
      default:
        throw new Error(`Unknown representation type: ${type}`);
    }
  }
 
  /**
   * Clears the segmentation-specific style for a given segmentation ID.
   * @param segmentationId - The ID of the segmentation to clear.
   */
  clearSegmentationStyle(segmentationId: string): void {
    if (this.config.segmentations[segmentationId]) {
      delete this.config.segmentations[segmentationId];
    }
  }
 
  /**
   * Clears all segmentation-specific styles.
   */
  clearAllSegmentationStyles(): void {
    this.config.segmentations = {};
  }
 
  /**
   * Clears the viewport-specific style for a given viewport ID.
   * @param viewportId - The ID of the viewport to clear.
   */
  clearViewportStyle(viewportId: string): void {
    if (this.config.viewportsStyle[viewportId]) {
      delete this.config.viewportsStyle[viewportId];
    }
  }
 
  /**
   * Clears all viewport-specific representation styles while preserving the renderInactiveSegmentations setting.
   */
  clearAllViewportStyles(): void {
    for (const viewportId in this.config.viewportsStyle) {
      const viewportStyle = this.config.viewportsStyle[viewportId];
      const renderInactiveSegmentations =
        viewportStyle.renderInactiveSegmentations;
      this.config.viewportsStyle[viewportId] = {
        renderInactiveSegmentations,
        representations: {},
      };
    }
  }
 
  /**
   * Clears both segmentation-specific and viewport-specific styles,
   * effectively resetting to global styles.
   */
  resetToGlobalStyle(): void {
    this.clearAllSegmentationStyles();
    this.clearAllViewportStyles();
  }
 
  /**
   * Checks if there is a non-global style for a given specifier.
   * @param specifier - The specifier object containing viewportId, segmentationId, type, and segmentIndex.
   * @returns True if there is a non-global style, false otherwise.
   */
  hasCustomStyle(specifier: {
    viewportId?: string;
    segmentationId?: string;
    type?: SegmentationRepresentations;
    segmentIndex?: number;
  }): boolean {
    const { type } = specifier;
    const style = this.getStyle(specifier);
    // Perform a deep comparison between the style and the default style
    const defaultStyle = this.getDefaultStyle(type);
    return !utilities.deepEqual(style, defaultStyle);
  }
}
 
const segmentationStyle = new SegmentationStyle();
 
export { segmentationStyle };