Documenting MedEye3d.jl iterations under Google-Summer-of-Code-2024

Divyansh GoyalDivyansh Goyal
22 min read

Mentor : Md, Phd Dr Jakub Mitura

( MedEye3d.jl Github Repository )

Dissection of the underlying components of MedEye3d.jl package for medical imaging visualizations :

MedEye3d.jl is a part of the medical imaging subecosystem of packages under the julia programming language, maintained and managed under the juliahealth community. It makes itself as a handy toolkit for visualizing medical imaging data (such as Nifti, Dicom and HDF5 [BIDS based]) natively within Julia.

Directory Structure of MedEye3d.jl

The gist of the MedEye3d.jl package is within the src directory, encompassing all of its implementation details and data structures. Since the entire package is simply the interaction of files between the structs and the display directory, we are going to parsing the functionality within julia files from such directories for better understanding the entire codebase.

Parsing Structs directory

BasicStructs.jl module

The BasicStructs modules defines some julia structs likely used for storing configuration setting, constants and results for some image processing or analysis tasks.

NOTE : the modules defines structs with the usage of the "@with_kw" macro from the Parameters.jl julia package. Each structure uses the @with_kw macro from the Parameters.jl package, allowing for easy creation of structs with default values and keyword constructors. This feature is particularly useful for configurations and settings, where only a subset of the fields might need to be specified by the user.

( @with_kw usage )

  1. ImageConstants : The structure holds constants associated with an image over which evaluations or segmentations will be performed.
    mvspx : (voxel spacing x)
    mvspy : (voxel spacing y)
    mvspz : (voxel spacing z), Represent the voxel spacing in x,y and z dimensions, respectively. Voxel spacing is a critical parameter in medical imaging, indicating the physical distance between the center of adjacent voxels in each dimension.
    isZConst : (boolean for slice thickness constant), A boolean value indicating whether the slice thickness in the z dimension is consistent across the entire image.
    ZPositions : (Array of true physical positions of slices relative to the beginning), A vector of floating-point numbers representing the true physical positions (in millimeters) of slices relative to the beginning of the image. This is used in cases where there is variable thickness of slices.
    numberOfVox : (number of voxels in image), The total number of voxels in the image

  2. Exploring the Fascinating Cross-Sectional Slices at the International Museum  of Surgical Science in Chicago

Perhaps each cross-section of the human body above is being treated as an image here in the medeye3d.jl pacakge, since each image seems to be having 3 dimensions x,y and z and not just two.

Schematic showing principle behind image acquisition in MRI. Voxels,... |  Download Scientific Diagram

  1. ConfigurationStruct , This structure is meant to hold configuration settings that specify which metrics the user is interested in calculating. Currently, it contains
    anyFuzzy : (any of the metric calculated fuzzy)

  2. ResultMetrics , (Struct holding all the resulting metrics , if some metric was not calculated its value is just -1), This structure is designed to store the results of various metrics calculations. If a particular metric was not calculated, its value defaults to -1.0. The metrics include:
    dice: Dice coefficient, a measure of overlap between two samples.

    jaccard: Jaccard coefficient, another measure of similarity and diversity between sample sets.

    gce: Global consistency error, likely a measure of segmentation consistency.

    vol: Volume metric, possibly measuring the volume of a segmented region.

    randInd: Rand Index, a measure of the similarity between two data clusterings.

    mi: Mutual information, a measure of the mutual dependence between variables.

    ic: Interclass correlation, likely a measure of the reliability of metrics involving different classes.

    Kc: Kohen Cappa (likely a misspelling of Cohen's Kappa), a statistic that measures inter-rater agreement for qualitative items.

    Mahalanobis: Mahalanobis distance, a measure of distance between a point and a distribution.

    Hausdorff: Hausdorff distance, a measure of how far two subsets of a metric space are from each other.

DataStructs.jl module

The DataStructs.jlmodule defines several Julia structures (structs) and one function, all designed to manage and store data for display purposes, possibly within a medical imaging context. Here's a breakdown of the key components:

Abstract Types

  • RawDataToDisp: An abstract type serving as a base for types that hold raw data intended for display.

  • DataToDisp: Another abstract type, likely serving as a base for types that hold data along with required metadata for display.

Mutable Structs

  • TwoDimRawDat: Represents 2-dimensional data for displaying a single slice. It is mutable to allow for rapid and multiple changes, especially in the context of mask data. Fields include:

    • type: The data type of the raw pixel data (default UInt8).

    • name: An associated name for the data.

    • dat: The raw pixel data stored in a 2-dimensional array.

  • ThreeDimRawDat: Similar to TwoDimRawDat, but for 3-dimensional data. It is also mutable for the same reasons. Fields include:

    • type: The data type of the raw voxel data (default UInt8).

    • name: An associated name for the data.

    • dat: The raw voxel data stored in a 3-dimensional array.

  • DataToScrollDims: Stores additional data about the full dimensions of scrollable data, necessary for efficiently switching slicing plane orientation. Fields include:

    • imageSize: The number of voxels in each dimension.

    • voxelSize: The physical size of each voxel.

    • dimensionToScroll: Indicates which dimension to scroll through for slicing.

  • FullScrollableDat struct is designed to manage and display scrollable data, particularly useful in contexts where data is represented in multiple slices, such as medical imaging. It inherits from the DataToDisp abstract type, indicating its role in displaying data. This struct is mutable, allowing for dynamic updates, especially useful when dealing with mask data that may change frequently. Here's a breakdown of its fields:

    • dataToScrollDims: An instance of DataToScrollDims, it stores additional information about the full dimensions of the scrollable data, such as the size of the image and the voxel size. This is crucial for efficiently switching the slicing plane orientation.

    • dimensionToScroll: An integer that specifies which dimension to scroll through. For example, if set to 3, and you have a slice number x, you would access the data as A[:,:,x]. This allows for flexible data exploration across different dimensions.

    • dataToScroll: A vector of ThreeDimRawDat instances. Each ThreeDimRawDat contains a name and the actual data to be passed. This field represents the core data that can be scrolled through slice by slice.

    • mainTextToDisp: A vector of SimpleLineTextStruct instances. This field is for text that will be displayed consistently across all slices. It does not change with scrolling and provides context or information that is relevant to the entire dataset.

    • sliceTextToDisp: A vector of vectors of SimpleLineTextStruct, where each inner vector is associated with a specific slice. This allows for displaying text that is relevant to the particular slice being viewed.

    • segmMetr: An instance of ResultMetrics, it stores the results of metrics calculated for the whole 3D image. This could include various measurements or analyses performed on the data.

    • segmMetrs: A vector of ResultMetrics, with each element representing the metrics for a specific slice. This array should be the same size as the number of slices in the passed data, allowing for slice-specific metric analysis.

    • nameIndexes: A dictionary mapping string names to integer indices. This is generated by the getLocationDict function, providing an efficient way to query data by name. It helps in quickly finding the index of a particular piece of data in the dataToScroll vector.

    • slicesNumber: An integer indicating the number of available slices. This is essential for navigating through the data and understanding its structure.

  • SingleSliceDat struct is designed for managing and displaying data related to a single slice of a potentially larger dataset. This struct is particularly useful in applications where data is visualized slice by slice, such as in medical imaging or volumetric data analysis. Here's a detailed breakdown of its fields and functionalities:

    • listOfDataAndImageNames: A vector of TwoDimRawDat instances. Each TwoDimRawDat contains a name and the corresponding 2D data slice. The name serves as an identifier for the data, making it easier to manage and reference specific slices or types of data (e.g., different modalities or processing stages of the image).

    • textToDisp: A vector of SimpleLineTextStruct instances. This field is used for storing text information that needs to be displayed alongside the slice. Each SimpleLineTextStruct could represent a line or block of text, such as annotations, measurements, or any other relevant information that should accompany the slice visualization.

    • segmMetr: An instance of ResultMetrics. This field holds metrics or results associated with the slice, which could include statistical analyses, segmentation quality metrics, or any other quantitative information derived from the slice data. This allows for the integration of analytical results directly into the data structure for easy access and display.

    • nameIndexes: A dictionary mapping string names to integer indices. This is generated by the getLocationDict function, which takes the listOfDataAndImageNames as input. The dictionary provides an efficient way to look up data by name, facilitating quick access to specific slices or data types within the struct.

    • sliceNumber: An integer indicating the slice's position or number within a larger dataset. This is useful when the SingleSliceDat is part of a scrollable series of slices, allowing for easy reference to its position in the sequence.

Functions

  • getLocationDict: Takes a vector of tuples, where the first element is a string (name) and the second is a RawDataToDisp type. It returns a dictionary mapping these names to their indices in the list. This function facilitates quick lookup of data by name.

Structs

  • SimpleLineTextStruct: Holds a line of text along with metadata for display purposes. Fields include:

    • text: The text to be displayed.

    • fontSize: The size of the letters.

    • extraLineSpace: Controls the spacing between lines; a value of 1 results in standard line spacing.

  • CalcDimsStruct is designed to calculate and manage dimensions and proportions necessary for correctly displaying images and text within a graphical user interface, such as one utilizing OpenGL for rendering. This struct plays a crucial role in ensuring that images and text are displayed with the correct aspect ratio, size, and position, taking into account the dimensions of the window and desired display proportions. Here's a detailed breakdown of its fields:

    • imageTextureWidth, imageTextureHeight: Integers specifying the dimensions of the main image texture. These dimensions are crucial for calculating how the image should be scaled and displayed within the window.

    • textTexturewidthh, textTextureheightt: Integers specifying the dimensions of the texture used for displaying text. Similar to the image texture dimensions, these are used to ensure that text is rendered correctly and legibly.

    • textTextureZeros: A matrix of UInt8 zeros with dimensions matching the text texture. This matrix is used to refresh the text texture, effectively clearing it before rendering new text.

    • windowWidth, windowHeight: Integers representing the dimensions of the window in which the textures are displayed. These dimensions are essential for calculating scaling and positioning of the image and text textures.

    • fractionOfMainIm: A float specifying the fraction of the window width allocated for displaying the main image. This allows for controlling how much space is dedicated to the image versus text or other elements.

    • heightToWithRatio: A float specifying the desired height-to-width ratio for the main image texture. This is used to prevent unnatural stretching of the image.

    • avWindWidtForMain, avWindHeightForMain: Integers calculating the available width and height in the window for the main image, based on the window dimensions and the fractionOfMainIm.

    • avMainImRatio: A float representing the ratio between the available height and width for the main image. This is compared to the actual required ratio to determine if adjustments are needed.

    • correCtedWindowQuadHeight, correCtedWindowQuadWidth: Integers representing the corrected dimensions of the main image display area in the window coordinate system, ensuring the image is displayed with the correct aspect ratio.

    • quadToTotalHeightRatio, quadToTotalWidthRatio: Floats representing the ratio of the corrected main image display area's dimensions to the total window dimensions. These ratios are crucial for scaling and positioning.

    • widthCorr, heightCorr: Floats used for OpenGL corrections, ensuring that the image and text are correctly positioned and scaled within the OpenGL coordinate system, which has a maximum height and width of 2.

    • windowWidthCorr, windowHeightCorr: Integers representing corrections in window coordinates, derived from the OpenGL corrections and the window dimensions.

    • mainImageQuadVert, wordsImageQuadVert: Vectors of Float32 storing calculated vertex positions for the main image and text display quads, essential for rendering in OpenGL.

    • mainQuadVertSize, wordsQuadVertSize: Integers representing the sizes of the data for glBufferData calls for the main image and text display quads, ensuring that the correct amount of data is allocated and used for rendering.

  • valueForMasToSetStruct is a simple structure designed to encapsulate information about the current value to be used for setting pixels in a mask during user interaction, such as mouse clicks. Here's a detailed breakdown of its fields:

    • value: An Int64 representing the value that will be used to set pixels in the mask. This could be, for example, an intensity value or a label identifier in segmentation tasks. The default value is set to 1, implying that without specification, the mask will be set to a value of 1 wherever the user interacts with it.

    • text: An instance of SimpleLineTextStruct, which is a structure presumably designed to hold text information. In this context, it is used to display a message or information about the current value set for the mask. The text dynamically includes the current value through string interpolation, indicating to the user what value will be applied to the mask upon interaction. The default text is "value of mask to set is $(value)", which provides a clear and direct indication of the current setting.

  • WindowControlStruct is designed to manage and control the display parameters of a window. Here's a detailed breakdown of its fields:

    • letterCode: A String representing a shorthand or hotkey (e.g., "F2") to activate predefined window settings. This allows for quick switching between different visualization modes or parameters.

    • min_shown_white: An Int32 specifying the minimum intensity value to be displayed as white. This threshold helps in adjusting the contrast and brightness of the image by setting which pixel values are considered bright enough to be displayed as white.

    • max_shown_black: An Int32 specifying the maximum intensity value to be displayed as black. Similar to min_shown_white, this sets the threshold for which pixel values are considered dark enough to be displayed as black, aiding in contrast adjustment.

    • toIncrease: A Bool indicating whether the threshold value is intended to be increased. This could be part of a user interface control for dynamically adjusting image display settings.

    • toDecrease: A Bool indicating whether the threshold value is intended to be decreased. This works in conjunction with toIncrease to provide a mechanism for fine-tuning display parameters.

    • upper: A Bool indicating if the upper threshold is the target of modification. This specifies whether adjustments are being made to the upper limit of the display window.

    • lower: A Bool indicating if the lower threshold is the target of modification. This specifies whether adjustments are being made to the lower limit of the display window.

    • maskContributionToChange: A Bool indicating whether the modification involves the mask contribution. This could relate to how a mask (e.g., for segmentation or highlighting specific features) influences the overall display settings.

  • AnnotationStruct is designed to control mouse interaction parameters for annotating images, such as adjusting the stroke width of a brush tool. Here's a detailed breakdown of its fields:

    • strokeWidthChange: An Int32 that controls the extent to which the stroke width is increased or decreased around the point clicked. This is crucial for applications that involve manual annotation or drawing on images, as it allows the user to adjust the size of the annotation tool based on the task at hand.

distinctColorsSaved.jl module

The variables listOfColors and longColorList from the distinctColorsSaved.jl represent collections of colors defined as tuples of RGB (Red, Green, Blue) values. Each tuple corresponds to a specific color, where the values range from 0 to 255, indicating the intensity of each color component.

Variables

  • listOfColors

    • Purpose: This variable is a set of 18 contrasting colors, curated for situations where distinctiveness and contrast are crucial. The selection is based on a source that originally provided 20 colors, but black and white were excluded, and red and yellow were moved to the end. This adjustment might be specifically tailored for displaying images where these colors need to be avoided or emphasized last, such as in pet images.

    • Content: It contains tuples representing RGB values of colors. For example, (230, 25, 75) represents a shade of red, (60, 180, 75) a shade of green, and so on. These colors are chosen to be easily distinguishable from one another, making them suitable for applications where different elements need to be colored distinctly, such as in graphs, charts, or maps.

longColorList

  • Purpose: This variable is a more extensive set of 256 distinct colors. It's designed for applications requiring a broader palette, such as generating GIFs with a 256-color palette. The source mentioned is a Stack Overflow post, indicating that the selection is optimized for diversity and distinctiveness within the limitation of 256 colors.

  • Content: Similar to listOfColors, it consists of tuples with RGB values, but with a much larger variety. The colors range from (184,129,131), a muted pink, to (200,149,197), a soft purple, and many others in between. This wide range ensures that there's a sufficient variety of hues and shades for detailed color-coding or imaging tasks requiring a comprehensive palette.

Above are some of the few structs definitions within the src of the MedEye3d.jl

These structures and the function are designed to facilitate the handling, display, and manipulation of medical imaging data or similar datasets. The use of mutable structs for the raw data types (TwoDimRawDat and ThreeDimRawDat) suggests that the data they contain is expected to change frequently, which is common in interactive visualization or editing applications.

Understanding callbacks on GLFW events

The current iteration of MedEye3d.jl reportedly have been encountering issues around lag and constant flickers, along with the infamous error around RandomAccessViolation Error.

The flicker got resolved by remove the @spawn :interactive macro from the relevant files within the src directory of MedEye3d.jl. Rather, than spawning interactive task for individual functions, we can simply aggregate all tasks within a Base.Channel which is arguably thread safe, and will resovle quite a lot of lag originating as a result of thread Actor from Rocket.jl.

The prominent task as of right now is to seamlessly aggregate input data generated as a result of the following callbacks:
GLFW.SetKeyCallback
GLFW.SetScrollCallback
GLFW.SetCursorPosCallback
GLFW.SetMouseButtonCallback,
and the data from these callbacks need to be passed within the channel field of our mainMedEye3d struct. The default function for the mainMedEye3d struct is the CoordinateDisplay function handling the main initialization stuff within the segmentationDisplay.jl file under "src->display->GLFW" directory.

The data from the Callbacks is registered to the channel queue in suitable types, such that, whenever a take! event happens on the channel (taking the data from the channel and removing it from the channel queue), inside of the consumer function in our main initialization function (i.e. coordinateDisplay)

The general idea here is to strip away the functionality of thread Actor [Rocket.jl] from MedEye3d and support Base.Channel for handling state, inputs and other data, rather than following the Actor event subscription model.

In order to understand the MedEye3d.jl functionality, the key files involved in order are :

Understanding the flow of function calls within our testing script will enable us to understand what components of the MedEye3d.jl package are being referenced and in what order.

  1. test_script_itk.jl

  2. SegmentationDisplay.jl

  3. PrepareWindow.jl

  4. PrepareWindowHelpers.jl

  5. ReactToInput.jl

  6. reactToKeyboard.jl

  7. ReactOnMouseClickAndDrag.jl

  8. ReactToScroll.jl

  9. ForDisplayStructs.jl

Rocket.jl involves reactive programming elements, allowing the usage of Actor to handle input events and their actions along with state management.

Understanding the SegmentationDisplay.jl file, the module SegmentationDisplay export 2 functions :
coordinateDisplay(), the coordinateDisplay function is the main initialization function handling the initial window context creation and state.
passDataForScrolling(), enable the scrollable data to be overlayed within the window created by the coordinateDisplay function.

Parsing SegmentationDisplay.jl file [Original, without channel support]

  1. Creation of the mainActor global variable, which handles all of the state, input event and their actions synchornously. It is an instance of the ActorWithOpenGlObject struct under ForDisplayStruct.jl file, which inherits fields from the NextActor from Rocket.jl. The sync() function makes the NextActor inherited struct synchronour as it wraps the ActoWithOpenGlObjcets in another "actor" struct.
    [Proposed changes] : removal of this mainActor as a global variable, rather it should originate as a new struct with a Channel field and should be returned from the coordinateDisplay initialization function, defined by the user. This coordinateDisplay function also expect data for scrolling, which later on needs to be passed from medimage. [MedImages.jl package under Julia]

  2. subscriptions = [], keeping track of all the subscription (connection between observable-> the source of event and the observer -> the consumer of event / event handler in reactive programming). Initlization (coordinateDisplay function) and the Glfw events are producers and all functions registered to rocket Actor are consumers. Functions which are registered to actor as consumers might have an append or push to this subscriptions array. No longer needed, with channel support.

  3. coordinateDisplay function, the initlization function,
    args : textureSpecificationArray, fractionofmainimage (the fraction telling how much width should the visualization take), data to scroll dimension.

Coordinatedisplay initialization [Original]:

-- checking if the mainActor.actor.forDisplayStruct.window type is GLFW.Window () (default array), then we need to cleanup().
-- setting appropriate texture object by making a newone using the setproperties function from Setfield.jl
-- setting up the CalcDimsStruct for neccessary constants for window. It uses pipe operator to update the immutable struct by creating and passing a new struct with the updated calucalted values for heighttowidth ratio and getMainVerticies.
-- Adding mainActor as an observer to observable event source CalcDimsStruct, and mainActor is also a struct with this field.
-- calling the PrepareWindow.displayAll() function based on the above texture ad CalcDimsStruct. The displayAll function defines an Atomic variable StopListenting (a variable that can accessed via multiple tasks) and and describes a @task with a loop to check if this StopListenting is true or not, until it listens for keyboard and mouse inputs every 0.005 seconds. then after creation of the task t, its executed sequentially by the scheduler using schedule function. The variables name followed by the [] is callable object calling into its call function. The StopListening[] Atomic variable will gives its current value.

Functions registered to actor :

here of() function produces an observable object (the event producer), and the mainActor is subscribed as an observer (the consumer).

COORDINATEDISPLAY:
Previously the following lines were subscribing data to the Actor with an observer, but now in order to invoke the relevant on_next! function, we simply need to put the data of the following types to our channel :

subscribe!(of(CalcDimsStruct), mainActor)
subscribe!(of(forDispObject), mainActor)
subscribe!(of(ForWordsDispStruct, mainActor)

now, have been changed into ,

put!(mainMedEye3d.channel, CalcDimsStruct)
put!(mainMedEye3d.channel, forDispObject)
put!(mainMedEye3d.channel, ForWordsDispStruct)

The flow of data from the window GLFW.PollEvents to the actor:

registerInteractions() -> subscribeGLFWtoActor() -> registerMouseScrollFunctions(), registerKeyboardFunctions(), registerMouseClickFunctions() -> subscribes the actions and returns [scrollsubscription, mouseClickSub, keyboardSub]

Invocation of registerInteractions() within coordinateDisplay initialization function.
# The holy grail of our aggregation of GLFW events and passing this data to the channel of the main Actor (we create).
[Queries]
-- the register functions registering callbacks to the Actor are consumers of the event source.
-- the register functions, involve the GLFW.SetCallback functions, but these functions do not return any data.
-- what are the next steps, instead of subscribing callbacks to the mainActor, should we add callbacks to the mainActor.Channel that we create.
-- so much stuff is happening around subscribing! to the mainActor, does all of that needs to be in the mainActor.Channel.

PASSDATAFORSCROLLING:
subscribe(of(FullscrollabeData),mainActor)

UPDATESINGLEIMAGESDISPLAYED:
subscribe!(of(SingeSliceDat), mainActor)

Making Channels log input events from callbacks for scroll, keyboard and mouse button and drag events.

in order to make channels work for the medeye3d.jl package, at first we simply added a new field titled "threadChannel" within the global variable mainActor. (potential name change required) in segmentationDisplay.jl initialization function (coordinateDisplay) named ActorWithOpenGlObjects (potential name change required as well).
The field was added:

@with_kw mutable struct ActorWithOpenGlObjects <:NextActor(Any):
... various field
#the following channel was added as a field
threadChannel :: Base.Channel{Any} = Base.Channel{Any}(1000)
#here the threadChannel is simply initialized with a size of 1000 objects of type Any.
end

Now, in order to provide the channel with producer functions (the functions which will simple add the data to the channe with put!), we modified the following functions under the reactingToMouseKeyboard directory :
registerMouseScrollFunctions
registerMouseClickFunctions
registerKeyboardFunctions,

which encompass the producer functions GLFW.Set{event}Callback. Here the callbacks are providing data based on the type of callback method used from GLFW, so as to provide a response mechanism to various types of events. Previously the callback function to these events was initiating a handler function upon several instances of the "Subscribable" structs defind within forDispUtils.jl file. These struct make the data objects with organised fields from raw input event data. Instead of invoking the handler function, we simply exploited the producer nature of the GLFW.Set{event}Callback functions, to put the input data inside of the channel queue.

#the following line was added within various register functions. that register callbacks around various input events
#example
function registerMouseScrollFunctions(...args, actor::SyncActor{Any,ActorWithOpenGlObjects})
#function stuff
GLFW.SetScrollCallback(window, (a, xoffset, yoffset)->begin
#putting the data inside of the channel
put!(actor.actor.threadChannel, (xoffset,yoffset))
# putting a tuple inside of the channel
end)
end

NOTE: the usage of SyncActor in rocket, wraps the actor object inside of another "actor" named instance.

Now we simply updated the function calls within the reactToInput.jl file to accomodate the actor as an argument to these functions.
Finally, under the segmentationDisplay.jl file we simply added the following line under the initialization function - coordinateDisplay:

function coordinateDisplay(...args)
mainActor = Sync(ActorWithOpenGlObjects()) 

Threads.@spawn @info logChannelData(mainActor)
end

Here is the global varibale mainActor, which is storing all of the state data and input events. So we simply ran new thread task to constantly log out the events from the channel. (Consumer). The logChannelData function got defind in the ForDisplayStruct.jl, defines the following function

function displayChannelData(actor::SyncActor{Any, ActorWithOpenGlObjects})
while true
data = take!(actor.actor.threadChannel)
@info "Data from the channel : $data"
end
end

And voil'a you will see the channel events being logged right as you run the develop and precompile MedEye3d, and load a sample script from the root directory, such as test_script_with_itk.jl.

Making Channel work with the reactive functionality in MedEye3d.jl [without Rocket components]

In order to to pass around the data from the Channel to the functions which update the data for display on the visualizer, a bunch of stuff is involved, but primarily the execution of on_next! functions take precedence. The following lines within SegmentationDisplay.jl file is taking precedence in the context of establishing reactivity and functionality with the help of data from the channel:

on_next!(stateObject::StateDataFields, data::Int64) =  reactToScroll(data,stateObject )
on_next!(stateObject::StateDataFields, data:: forDisplayObjects) =  setUpMainDisplay(data,stateObject)
on_next!(stateObject::StateDataFields, data:: ForWordsDispStruct) =  setUpWordsDisplay(data,stateObject)
on_next!(stateObject::StateDataFields, data::CalcDimsStruct) =  setUpCalcDimsStruct(data,stateObject)
on_next!(stateObject::StateDataFields, data::valueForMasToSetStruct) =  setUpvalueForMasToSet(data,stateObject)
on_next!(stateObject::StateDataFields, data::FullScrollableDat) =  setUpForScrollData(data,stateObject)
on_next!(stateObject::StateDataFields, data::SingleSliceDat) =  updateSingleImagesDisplayedSetUp(data,stateObject)
on_next!(stateObject::StateDataFields, data::MouseStruct) =  reactToMouseDrag(data,stateObject) #needs modification , with the react_to_draw, data of vectorStruct (MoustStruct)
on_next!(stateObject::StateDataFields, data::KeyInputFields) =  reactToKeyInput(data,stateObject)

Multiple lines as such in the code, but for now we need to focus on the above, as to make sure that event and callback related state modification happens from the Channel..

Action Item 1: initlization of the MainMedEye3d struct fomr the coodinateDisplay function. [DONE]

--returns a MainMedEye3d struct type object with a field channel.

Action Item 2: Separating out the state from the channel by establishing their own dedicated structures. [DONE]

-- Instead of managing a mainMedEye3d struct with 2 fields channel and state, we have separated the state as an Instance of a struct named "StateDataFields" with all the relevant field comoponents of the state defined there-in. Passing and playing around with the instance of this state structure happens inside of the consumer function for our channel.
-- The State Instance does not get passed around anyhwere except for its reference being passed only inside of a consumer function for the channel.

Action Item 3: Making state for input events get modified from the channel with the help of a consumer function. [DONE]

--definition of a function that encapsulates the data from the channel and updates the state value fields of the stateDataStruct, with the help of an on_next! function defined with multiple times so as to invoke the correct function for reaction, with the help of the julia multiple dispatch mechanism.

Putting a consumer function to modify the state data struct from a function in coordinateDisplay, and execute the function separately on an interactive thread, so as to ensure whenever a put! event happens in the channel , the state will be modified.

0
Subscribe to my newsletter

Read articles from Divyansh Goyal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Divyansh Goyal
Divyansh Goyal

Microsoft Learn Student Ambassador Sentient Being | Julia Lang admirer