Aurora Documentation
Official Hallgrímsson Lab Aurora documentation corpus (methods and tutorial transcript). Generated for search / LLM assistants.
Entries: 163. Generated: 2026-11-05T23:15:28+00:00.
Formats: HTML · plain text · JSON · interactive docs
Tutorial Transcript
Getting started
Introduction
Keywords: tutorial, transcript, video, youtube, introduction
Summary
Walkthrough of Aurora (MouseMorph): a free, batch-oriented pipeline that unifies rigid alignment, elastic registration, 3D segmentation, and landmarking so large morphometric image datasets stay reproducible from raw scans to structured outputs.
Algorithm / details
Biological imaging gives us extraordinary views inside living systems. But the real challenge is turning those images into structured meaningful data. If you've ever worked with 3D biological or medical images, you know how quickly the workflow becomes fragmented. Cleaning files, aligning samples, running processes, creating annotations, organizing outputs, and repeating the same steps across dozens or hundreds of specimens. Along the way, data sets can become messy, naming conventions drift, and different tools or scripts are often needed just to move from one step to the next. Today, I want to present a free-to-use platform that was developed to bring many of these steps together into a single automated pipeline. Operations such as rigid alignment, elastic registration, 3D segmentation, and landmarking can be run in batch across the entirety of your data sets with sensible defaults and semi-automated methods that reduce the need for high manual tuning. The goal is to make complex image processing workflows easier to run in a reproducible way, especially when working with large data sets. While the main purpose of these tools are to be used in morphometric research, the pipeline can also support other applications such as data set preparation, volumetric measurements, or preprocessing for machine learning workflows. My name is Alejandro Gutierrez. I am a biomedical imaging specialist at the Alberta Children's Hospital Research Institute, and I'm the primary developer of this software. In this tutorial, I'll walk through how to install the platform and run a complete processing pipeline from raw image to structured outputs. So, to set it up, first you go to the Helgason Lab.ca website.
Related: tutorial-ch02
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=0s · chapter 00:00
Tutorial transcript
- 0.56s Biological imaging gives us extraordinary views inside living systems. But the real challenge is turning those images into structured meaningful data.
- 15.6s If you've ever worked with 3D biological or medical images, you know how quickly the workflow becomes fragmented.
- 23.8s Cleaning files, aligning samples, running processes, creating annotations, organizing outputs, and repeating the same steps across dozens or hundreds of specimens.
- 37.56s Along the way, data sets can become messy, naming conventions drift, and different tools or scripts are often needed just to move from one step to the next. Today, I want to present a free-to-use platform that was developed to bring many of these steps together into a single
- 58.12s automated pipeline. Operations such as rigid alignment, elastic registration, 3D segmentation, and landmarking can be run in batch across the entirety of your data sets with sensible defaults and semi-automated methods that reduce the need for high manual tuning. The goal is to make complex image processing
- 82.12s workflows easier to run in a reproducible way, especially when working with large data sets. While the main purpose of these tools are to be used in morphometric research, the pipeline can also support other applications such as data set preparation, volumetric measurements, or
- 102.56s preprocessing for machine learning workflows. My name is Alejandro Gutierrez. I am a biomedical imaging specialist at the Alberta Children's Hospital Research Institute, and I'm the primary developer of this software. In this tutorial, I'll walk through how to install the platform and run a complete
- 121.88s processing pipeline from raw image to structured outputs. So, to set it up, first you go to the Helgason Lab.ca website.
Setting up an account
Keywords: tutorial, transcript, video, youtube, setting up an account
Summary
Create a Hallgrímsson Lab account, confirm your email, sign in (optional 2FA), then open the MouseMorph web application from the tools area.
Algorithm / details
Here, you will see in the top right, there's a login button. You go there. And if you don't have an account, you can go and click sign up, fill in your information, and then check your email to confirm. Uh make sure you check on the spam folder. Once I approve you, you can come back here, put in your credentials, sign in. You will see there's an optional two-factor authentication. I have it set up for myself, but you can decide if you want it or not. Once you're in, your name will show up on the top right, and you will see these three new options and in the web applications area. The toolkit that I'm going to show you is called Must Morph now. You click on that.
Related: tutorial-ch01, tutorial-ch03
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=136s · chapter 02:16
Tutorial transcript
- 137.48s Here, you will see in the top right, there's a login button. You go there. And if you don't have an account, you can go and click sign up, fill in your information, and then check your email to confirm.
- 153.72s Uh make sure you check on the spam folder. Once I approve you, you can come back here, put in your credentials, sign in.
- 171.32s You will see there's an optional two-factor authentication. I have it set up for myself, but you can decide if you want it or not.
- 187.4s Once you're in, your name will show up on the top right, and you will see these three new options and in the web applications area.
- 196.72s The toolkit that I'm going to show you is called Must Morph now. You click on that.
Download and install the software
Keywords: tutorial, transcript, video, youtube, download and install the software
Summary
Download the OS-specific installer from the site, run the setup wizard, launch the MouseMorph app so its local terminal starts, then refresh the website to connect to the running client.
Algorithm / details
You can hide the menu, and you will see that there will be a button here to download the software. Depending on what operating system you're in, you're going to see a different button appear here. So, then you can go ahead and click to download. It's downloading now. Once it's downloaded, you can go ahead and click on it. The wizard will open. You can hit next, next, install. I'm going to skip this part since I have it already installed. Once the installation successfully finished, you can go to start, search for MouseMorph app and you'll see it there. You can just go ahead and click on it. What you'll see is that a terminal will open and a bunch of information will be shown here. So, what we do is we refresh the website. We can now go ahead and hide this top part by clicking on this arrow.
Related: tutorial-ch02, tutorial-ch04
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=205s · chapter 03:25
Tutorial transcript
- 205.0s You can hide the menu, and you will see that there will be a button here to download the software.
- 216.56s Depending on what operating system you're in, you're going to see a different button appear here.
- 222.52s So, then you can go ahead and click to download. It's downloading now. Once it's downloaded, you can go ahead and click on it.
- 232.6s The wizard will open. You can hit next, next, install. I'm going to skip this part since I have it already installed. Once the installation successfully finished, you can go to start, search for MouseMorph
- 247.32s app and you'll see it there. You can just go ahead and click on it. What you'll see is that a terminal will open and a bunch of information will be shown here. So, what we do is we refresh the website. We can now go ahead and hide
- 261.84s this top part by clicking on this arrow.
File exploration and bundle detection (compatible formats)
Keywords: tutorial, transcript, video, youtube, file exploration and bundle detection (compatible formats)
Summary
Use the left-hand file explorer to browse your machine; Aurora auto-detects image bundles (TIFF folders, NIfTI, DICOM, Scanco AIM, multi-page TIFFs) and shows per-bundle summary info.
Algorithm / details
Uh first time you open it, it might take a while for uh the left side to load. Here, you can navigate some of the main paths in your computer. Uh for example, here I am in my E drive. So, I just clicked on the E drive and then went to my folder called Embryos Test video. So, for example, if I go to the home folder, it finds a bunch of files and documents. However, there's no bundles, which it shows it up here. What the bundles are is this is automatically detecting if in such folder there are any files or folders that look like medical images. In this case, it found that I have two TIFF folders and then one NIFTI image. Beyond these two file types, it can also accept DICOMs and the ScanCo machine AIM file as well as multi-page TIFFs. So, now the bundles will show up here and they will show some summary information of the images.
Related: tutorial-ch03, tutorial-ch05
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=264s · chapter 04:24
Tutorial transcript
- 264.36s Uh first time you open it, it might take a while for uh the left side to load. Here, you can navigate some of the main paths in your computer.
- 273.16s Uh for example, here I am in my E drive. So, I just clicked on the E drive and then went to my folder called Embryos Test video.
- 284.6s So, for example, if I go to the home folder, it finds a bunch of files and documents. However, there's no bundles, which it shows it up here. What the bundles are is this is automatically detecting if in such folder there are any files or folders that look like medical images. In this case, it found
- 305.6s that I have two TIFF folders and then one NIFTI image. Beyond these two file types, it can also accept DICOMs and the ScanCo machine AIM file as well as multi-page TIFFs. So, now the bundles will show up here and they will show
- 324.44s some summary information of the images.
Extract all scans
Keywords: tutorial, transcript, video, youtube, extract all scans
Summary
Run Extract all scans to unpack bundles into viewable volumes—watching the terminal for voxel-size and isotropy handling—then use cyan/yellow status icons and the processing tools panel on the right.
Algorithm / details
If we click on them, it'll say that to visualize the scan, we need to actually click on extract all scans, which is this first button that we see up here. So, we can go ahead and click on extract all scans. As we wait for it to complete extracting, I I to show you what's happening behind the scenes. So, if we click on the terminal, we can see here that a a bunch of things are being printed. If there's any issues you encounter with the software, please copy anything that got printed and send it to us. One thing that you can observe from this back-end functions running here is that we are fetching voxel sizes from the files and the headers. And we are determining if the voxels are isotropic. So, sometimes when we have MRI scans, you often find that the interslice distance is higher. So, that anisotropy will be calculated here. And then we interpolate using a registration method so that we can get isotropic images in the end. The speed is going to be determined by how powerful your computer is and the amount of tools you can use will be determined by how much RAM memory you have available. We suggest having at least 64 GB of RAM, but anything above that will be highly beneficial, especially for a larger scans. Once it is done, the page will refresh automatically. You will see that the images got extracted successfully because the icons here on the left will show in cyan. Otherwise, if they were partially extracted due to an error, you will see them in color yellow. So, we can go ahead now and click on the first one. Okay, so now we're going to focus on the right side of the interface. Um the first thing we see is a block here with a lot of options. These are all the processing steps that we can run on our images. Most of them work on batch level, so all the images others uh can be set up to work only on specific selected images.
Related: tutorial-ch04, tutorial-ch06
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=327s · chapter 05:27
Tutorial transcript
- 327.28s If we click on them, it'll say that to visualize the scan, we need to actually click on extract all scans, which is this first button that we see up here.
- 339.12s So, we can go ahead and click on extract all scans. As we wait for it to complete extracting, I I to show you what's happening behind the scenes. So, if we click on the terminal, we can see here that a a bunch of things
- 352.76s are being printed. If there's any issues you encounter with the software, please copy anything that got printed and send it to us. One thing that you can observe from this back-end functions running here is that we are fetching voxel sizes from the
- 373.76s files and the headers. And we are determining if the voxels are isotropic. So, sometimes when we have MRI scans, you often find that the interslice distance is higher.
- 388.08s So, that anisotropy will be calculated here. And then we interpolate using a registration method so that we can get isotropic images in the end. The speed is going to be determined by how powerful your computer is and the amount of tools you can use will be determined by how much
- 411.8s RAM memory you have available. We suggest having at least 64 GB of RAM, but anything above that will be highly beneficial, especially for a larger scans. Once it is done, the page will refresh automatically.
- 428.6s You will see that the images got extracted successfully because the icons here on the left will show in cyan.
- 437.16s Otherwise, if they were partially extracted due to an error, you will see them in color yellow. So, we can go ahead now and click on the first one.
- 445.64s Okay, so now we're going to focus on the right side of the interface. Um the first thing we see is a block here with a lot of options. These are all the processing steps that we can run on our images. Most of them work on batch level, so all the images others uh can be set up to work only on specific
- 465.84s selected images.
Reference scan selection importance
Keywords: tutorial, transcript, video, youtube, reference scan selection importance
Summary
Pick a clean, average-looking reference scan carefully: later batch tools largely copy the reference’s rotation, crop, intensity, and other edits onto the rest of the dataset.
Algorithm / details
Then we have a reference scan block. Here you're supposed to choose a scan that looks like your most clean and average-looking scan. Selecting the reference scan is a non-trivial process because a lot of the different tools we have up here will rely on this reference scan to make their job. Basically, a lot of the steps will try to imitate whatever you already did to the reference scan, such as rotating it, scaling it, cropping it, changing the intensity levels, etc. And then in the
Related: tutorial-ch05, tutorial-ch07
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=467s · chapter 07:47
Tutorial transcript
- 468.24s Then we have a reference scan block. Here you're supposed to choose a scan that looks like your most clean and average-looking scan. Selecting the reference scan is a non-trivial process because a lot of the different tools we have up here will rely on this reference scan to
- 488.84s make their job. Basically, a lot of the steps will try to imitate whatever you already did to the reference scan, such as rotating it, scaling it, cropping it, changing the intensity levels, etc. And then in the
Preview block overview
Keywords: tutorial, transcript, video, youtube, preview block overview
Summary
The preview block shows edit history for the current scan (starting from the raw extract) and stacks each new processing step as you work.
Algorithm / details
bottom, we can see the preview block. In the preview block, we have different things. First, on the top left, we have some summary information of the image with whichever edits we will make to the image. Here we, as we just extracted it, we only see the raw edit. But then, every time we do something to the image, we will see new edits stack on here. On
Related: tutorial-ch06, tutorial-ch08
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=501s · chapter 08:21
Tutorial transcript
- 502.84s bottom, we can see the preview block. In the preview block, we have different things. First, on the top left, we have some summary information of the image with whichever edits we will make to the image.
- 516.72s Here we, as we just extracted it, we only see the raw edit. But then, every time we do something to the image, we will see new edits stack on here. On
Threshold adjustment and mesh generation
Keywords: tutorial, transcript, video, youtube, threshold adjustment and mesh generation
Summary
Tune the marching-cubes mesh with the histogram’s yellow threshold bar, refresh to inspect the surface, then save so the chosen threshold sticks with the scan.
Algorithm / details
the right side, we see the mesh that got automatically generated by using a threshold, and then some options to process the image that rely on the mesh visualization to execute. Below, you will see a histogram. The histogram represents the distribution of intensity values of the image. And then you will see a vertical yellow bar. This vertical yellow bar represents the threshold that is currently used to generate this mesh. So, if we click on threshold mode up here, now we will see that this is the actual threshold that is generating this mesh using uh marching cubes algorithm, if you're curious. So, now we can change the threshold by sliding this yellow bar here. We will try to find a threshold that correctly encapsulates all the different anatomy without introducing a lot of noise, as we can see when we get too close to the background here. So, this should be good. Before you save the value, if you want to check how that looks on the mesh, you can go in the top right here and click refresh. As you can see, now we see a different mesh. Now, there's less holes around it cuz the previous threshold was too restrictive is when it looks better. If you want to see the mesh in up closer, you can go and toggle full screen down here. And now we can see the full mesh in full screen. If you are happy with this threshold, then you can just hit the save icon. The website reloads, and here we have it. The scan now saved the threshold.
Related: tutorial-ch07, tutorial-ch09
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=528s · chapter 08:48
Tutorial transcript
- 528.92s the right side, we see the mesh that got automatically generated by using a threshold, and then some options to process the image that rely on the mesh visualization to execute. Below, you will see a histogram. The histogram represents the distribution of intensity
- 547.04s values of the image. And then you will see a vertical yellow bar. This vertical yellow bar represents the threshold that is currently used to generate this mesh.
- 558.64s So, if we click on threshold mode up here, now we will see that this is the actual threshold that is generating this mesh using uh marching cubes algorithm, if you're curious.
- 571.48s So, now we can change the threshold by sliding this yellow bar here. We will try to find a threshold that correctly encapsulates all the different anatomy without introducing a lot of noise, as we can see when we get too close to the
- 589.84s background here. So, this should be good. Before you save the value, if you want to check how that looks on the mesh, you can go in the top right here and click refresh. As you can see, now we see a different mesh. Now, there's less holes around it cuz the previous threshold was
- 611.2s too restrictive is when it looks better. If you want to see the mesh in up closer, you can go and toggle full screen down here.
- 622.12s And now we can see the full mesh in full screen. If you are happy with this threshold, then you can just hit the save icon.
- 635.24s The website reloads, and here we have it. The scan now saved the threshold.
Voxel image viewer (adjusting intensity value preview)
Keywords: tutorial, transcript, video, youtube, voxel image viewer (adjusting intensity value preview)
Summary
Navigate sagittal, axial, and coronal slice views with crosshairs to read voxel intensities, and use the histogram display range (with percentile labels) to preview contrast before normalization.
Algorithm / details
And last but not least, we have at the bottom here of the preview the intensity values. So, we have three windows for the different sagittal, axial, and coronal views. If we click and drag on any of the three windows, we can slide around with the crosshairs to see all the different slices. At the intersection of the crosshairs, it will calculate what the unit for that voxel is, and it will show you that on the top left here. So, for example, we see that this background area here has values of around 11,000. So, what you can do now is to visualize it a little bit cleaner. You can go up here to the histogram and drag the slider on the bottom until we reach that 11,000. Now you see that the background is completely black. And you can do the same for the foreground. So the top slider you can bring it down. And now the range of intensities will be more evenly distributed from black to white. Anything that lies outside of the range that you visualize here will just go black. So bear that in mind. Finally, something that you can see here in this histogram is that the sliders have a percentage on top of them. This percentage represents the percentile given by the distribution of intensity values at that specific value. This will become helpful later when we want to normalize the values. Okay,
Related: tutorial-ch08, tutorial-ch10
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=640s · chapter 10:40
Tutorial transcript
- 640.16s And last but not least, we have at the bottom here of the preview the intensity values. So, we have three windows for the different sagittal, axial, and coronal views.
- 652.16s If we click and drag on any of the three windows, we can slide around with the crosshairs to see all the different slices.
- 670.2s At the intersection of the crosshairs, it will calculate what the unit for that voxel is, and it will show you that on the top left here.
- 682.12s So, for example, we see that this background area here has values of around 11,000.
- 691.76s So, what you can do now is to visualize it a little bit cleaner. You can go up here to the histogram and drag the slider on the bottom until we reach that 11,000.
- 707.92s Now you see that the background is completely black. And you can do the same for the foreground. So the top slider you can bring it down.
- 721.52s And now the range of intensities will be more evenly distributed from black to white. Anything that lies outside of the range that you visualize here will just go black.
- 733.12s So bear that in mind. Finally, something that you can see here in this histogram is that the sliders have a percentage on top of them.
- 741.36s This percentage represents the percentile given by the distribution of intensity values at that specific value.
- 749.32s This will become helpful later when we want to normalize the values. Okay,
Important quality checks (how to mark scans as faulty | use grid view)
Keywords: tutorial, transcript, video, youtube, important quality checks (how to mark scans as faulty | use grid view)
Summary
QC the cohort: flag bad scans as faulty (so batch tools skip them), use grid view to compare projections/meshes/thresholds, and fix per-scan thresholds or hide faulty items as needed.
Algorithm / details
first thing I'm going to do is I'm going to check the images themselves. I'm going to see that the quality is good enough. If any of them show any signs of artifacts or undesired anatomy, then I can come and mark them as faulty. To do that you go on the right side of the title and there's this button here called report as faulty. It'll prompt you to confirm. You hit okay and then you'll see it reloads and now it shows you this icon on the right side of of the scan saying this scan has been marked as faulty. What this means is that for every single processing step that you can run, it will actively ignore this particular scan without actually deleting it if you want to revisit it at some point in the future. Something else you can do here is hide all of them. So, in the working directory on the top, there's a button that says hide faulty scans. You click that, it'll hide it. If you change your mind, you can come back to the scan and click on this again, and it will say, "Do you want to unflag this scan?" You can say okay. It will reload. And now we're back to where we started. Part of this initial revision of all the scans will have to include checking the automatic thresholds that they got assigned for their own meshes. We already went through the main image here, but you have two options. You either click on all the subsequent scans one by one to check the threshold, which would be the highest quality way to test this. Or, you can go ahead and click on grid view, which is in the top right corner of the working directory block. So, now that they loaded, we can see them all side by side. This grid view shows you several things. First, it shows you a projection of one of the axis, essentially flattening the image as if it was an X-ray. Then, you see the other two different views at their center slice. And then, you see a preview of the marching cubes 3D mesh. But, this is all static. You cannot rotate the mesh or anything like that. And it's a low quality mesh. So, don't rely too much on it. You can also see a preview of the histogram for the image and a preview of the yellow vertical bar representing the threshold. So, for example here, we can see that this scan on the right side has a very decent looking threshold, but the one on the bottom here looks like very destructive. One way to quickly inspect this is we go on the right side here, and we have an option that says threshold selection. We toggle that on, and we actually see a preview of the thresholds on these bottom images, and we can adjust individual thresholds. So, for example, the bottom image that got a wrong one, we click once anywhere in the histogram, and now the yellow vertical bar follows my mouse. Okay, so now I can test by moving it a little closer to the background peak. So, I click again, and that shows me how the threshold moved. Again, this is applying it to the whole image, but it's actually only true for this bottom row. So, I'm going to adjust it a little more. Somewhere in there looks good. So, now I'm going to go ahead on the right side and hit save threshold values, which will reload it. Now, if we go and click on grid view again, it will reprocess whichever images had some changes, but automatically load back any previews that had no changes made to them. Here we go, we're back here, and now we see that the preview of the mesh that it generated with marching cubes looks way better. You shouldn't still fully rely on this. This is decent, and I can work with this, but if you are very interested in having a very accurate mesh, you should definitely go and click on the image and use the different tools that we have on the preview. One thing that we can notice here, for example, is that this bottom left scan it kind of looks like the contrast at the center of the image is washed out compared to the outer edges. This could have probably been due to different factors during scanning, during the staining of the sample, etc. If we feel like this is something we don't want to work with, we can also flag it as faulty in the grid view by clicking on the top right icon. For now, I'm going to keep it because I'm only interested in the outer mesh for the analysis I'm going to run. Other things you can do in this view are zooming in into the different quadrants. So, for example, if I only want to see the 3D mesh preview, I can hit on this one and it'll just zoom in.
Related: tutorial-ch09, tutorial-ch11
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=753s · chapter 12:33
Tutorial transcript
- 753.72s first thing I'm going to do is I'm going to check the images themselves. I'm going to see that the quality is good enough. If any of them show any signs of artifacts or undesired anatomy, then I can come and mark them
- 769.36s as faulty. To do that you go on the right side of the title and there's this button here called report as faulty.
- 777.36s It'll prompt you to confirm. You hit okay and then you'll see it reloads and now it shows you this icon on the right side of of the scan saying this scan has been marked as faulty.
- 789.84s What this means is that for every single processing step that you can run, it will actively ignore this particular scan without actually deleting it if you want to revisit it at some point in the future.
- 806.4s Something else you can do here is hide all of them. So, in the working directory on the top, there's a button that says hide faulty scans.
- 813.88s You click that, it'll hide it. If you change your mind, you can come back to the scan and click on this again, and it will say, "Do you want to unflag this scan?" You can say okay.
- 825.44s It will reload. And now we're back to where we started. Part of this initial revision of all the scans will have to include checking the automatic thresholds that they got assigned for their own meshes.
- 839.84s We already went through the main image here, but you have two options. You either click on all the subsequent scans one by one to check the threshold, which would be the highest quality way to test this.
- 852.68s Or, you can go ahead and click on grid view, which is in the top right corner of the working directory block. So, now that they loaded, we can see them all side by side.
- 862.96s This grid view shows you several things. First, it shows you a projection of one of the axis, essentially flattening the image as if it was an X-ray. Then, you see the other two different views at their center slice. And then, you see a
- 877.08s preview of the marching cubes 3D mesh. But, this is all static. You cannot rotate the mesh or anything like that.
- 882.64s And it's a low quality mesh. So, don't rely too much on it. You can also see a preview of the histogram for the image and a preview of the yellow vertical bar representing the threshold. So, for example here, we can see that this scan on the right side has a very decent looking
- 902.92s threshold, but the one on the bottom here looks like very destructive. One way to quickly inspect this is we go on the right side here, and we have an option that says threshold selection.
- 915.4s We toggle that on, and we actually see a preview of the thresholds on these bottom images, and we can adjust individual thresholds. So, for example, the bottom image that got a wrong one, we click once anywhere in the histogram, and now the yellow vertical bar follows my mouse. Okay, so now I can
- 935.44s test by moving it a little closer to the background peak. So, I click again, and that shows me how the threshold moved.
- 943.84s Again, this is applying it to the whole image, but it's actually only true for this bottom row. So, I'm going to adjust it a little more. Somewhere in there looks good. So, now I'm going to go ahead on the right side and hit save
- 957.36s threshold values, which will reload it. Now, if we go and click on grid view again, it will reprocess whichever images had some changes, but automatically load back any previews that had no changes made to them. Here we go, we're back here, and now we see that the preview of the mesh that it
- 974.72s generated with marching cubes looks way better. You shouldn't still fully rely on this. This is decent, and I can work with this, but if you are very interested in having a very accurate mesh, you should definitely go and click on the image and use the different tools that we have on the preview. One thing
- 991.8s that we can notice here, for example, is that this bottom left scan it kind of looks like the contrast at the center of the image is washed out compared to the outer edges. This could have probably been due to different factors during scanning, during the staining of the sample, etc. If we feel like this is
- 1011.4s something we don't want to work with, we can also flag it as faulty in the grid view by clicking on the top right icon.
- 1018.84s For now, I'm going to keep it because I'm only interested in the outer mesh for the analysis I'm going to run. Other things you can do in this view are zooming in into the different quadrants.
- 1032.64s So, for example, if I only want to see the 3D mesh preview, I can hit on this one and it'll just zoom in.
Choosing a reference after the quality checks
Keywords: tutorial, transcript, video, youtube, choosing a reference after the quality checks
Summary
After QC, choose the most representative scan (good contrast in regions of interest) from the reference dropdown; a ruler icon marks it as the active reference.
Algorithm / details
Finally, what I can see from here is that I actually like this first image to be the reference. It looks pretty average. It looks like there's a decent amount of contrast, especially in the areas I'm interested in, which is the head and the face here. So, then I can go ahead and click on it. Now, since I like this one, I can just go and select it from the drop-down menu here in the reference scan block. And you will see that it adds this ruler icon on the top right here. So, now the
Related: tutorial-ch10
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1047s · chapter 17:27
Tutorial transcript
- 1047.32s Finally, what I can see from here is that I actually like this first image to be the reference. It looks pretty average. It looks like there's a decent amount of contrast, especially in the areas I'm interested in, which is the
- 1060.52s head and the face here. So, then I can go ahead and click on it. Now, since I like this one, I can just go and select it from the drop-down menu here in the reference scan block.
- 1072.76s And you will see that it adds this ruler icon on the top right here. So, now the
Processing the reference scan
Set 3D Rotation manually
Keywords: tutorial, transcript, video, youtube, set 3d rotation manually
Summary
Manually rotate the reference in mesh rotate mode using axis guides and the 3×3 matrix, then apply (usually to this scan only) so both mesh and voxels share a standardized orientation.
Algorithm / details
first thing we want to do is we want to realign this scan to fit our standardized criteria. To do that, we first should apply a rotation. That will be by going here in the mesh to rotate mode. This should only be necessary for your reference scan, unless there's something special that needs to be done for any others. You can see now that we have three sliders up here. Each of the sliders rotates a different axis. On the left, we see a window with a 3x3 matrix. This matrix represents the rotation we want to apply. We can go ahead and go into full screen view to make it easier for us to run this step. Within this view, we can start moving the sliders around, rotating the mesh. And as we do that, we see that the rotation matrix starts to change. If we want to reset to the original position, we can just click reset here and we go back to the original orientation. So, what I like to do to get a very good rotation here is I go first down here and hover over my three different views, which is to align it to Z, Y, and X. The first one I do, I like to do Z, which is the front facing. As you can see, uh in this one, we get a diagram that shows us that the specimen should be facing us. And then the blue circle will be shown around it, right? So, we click this and it automatically reorients us to that position. Here, what I do first is I quickly try to make the specimen be aligned as it suggested. I still don't try to make it perfect, just very roughly looking at me. Okay. Now, I go to the Y view. Now, in the Y view, you can see it's a top-down view and it shows us that we should be looking at the specimen facing down. So, now we focus on the green specifically until we're happy with it. Then, we go to the X view, which tells us that the specimen should be looking to the left. Here, I'm going to focus then only on the red ring and I'm going to align it until I see a position where I would like in the future to crop the image. What do I mean with this? Is if I were to crop the image from the bottom, would it include part of the bottom of the mouth here that I want to keep or not? So, for example, if I did this and then I crop the bottom, I would likely have to crop part of the mouth to get higher on the neck here. So, I like this one. And once I'm happy with this, I go back to Z, so the original, and I fix the final Z alignment. Okay, this looks pretty symmetrical to me. Now, I'm being more picky about it, fine-tuning it. I go to Y again. Y looks pretty good. And then X again, and X looks pretty good. Okay, so now I'm happy with it. Now, we can apply this rotation. You can either apply it to this scan alone or to all the scans. If you apply it to all the scans, essentially what you're saying is that all of the scans were already in the same position. That is not the case here as you saw. Every scan is in a different position. So, I'm going to only apply it to this scan. You shouldn't need to touch the interpolation method. It will show you that if you change it to nearest neighbor, that is only necessary for binary images. That is if you were applying this rotation for a segmentation mask, for example. So, we keep it as linear, and then we just hit apply the scan, and then we just wait. Okay, so now that it finished, we can go ahead and click on it. One thing you can notice is that now on the same bundle, now you have this little icon here that says 3D rotated. This will be indicating each edit that you do to any of the images. This literally means that a new file got created that has the 3D rotation integrated on it. As you see now, the mesh is nicely aligned, but also the voxel image down here is very nicely aligned.
Related: tutorial-ch13
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1078s · chapter 17:58
Tutorial transcript
- 1078.72s first thing we want to do is we want to realign this scan to fit our standardized criteria. To do that, we first should apply a rotation. That will be by going here in the mesh to rotate mode. This should only be necessary for your reference scan, unless there's something special that needs to be done
- 1098.4s for any others. You can see now that we have three sliders up here. Each of the sliders rotates a different axis.
- 1106.96s On the left, we see a window with a 3x3 matrix. This matrix represents the rotation we want to apply. We can go ahead and go into full screen view to make it easier for us to run this step. Within this view, we can start moving the sliders around,
- 1125.56s rotating the mesh. And as we do that, we see that the rotation matrix starts to change.
- 1134.24s If we want to reset to the original position, we can just click reset here and we go back to the original orientation. So, what I like to do to get a very good rotation here is I go first down here and hover over my three different views,
- 1150.76s which is to align it to Z, Y, and X. The first one I do, I like to do Z, which is the front facing.
- 1159.88s As you can see, uh in this one, we get a diagram that shows us that the specimen should be facing us.
- 1168.04s And then the blue circle will be shown around it, right? So, we click this and it automatically reorients us to that position. Here, what I do first is I quickly try to make the specimen be aligned as it suggested.
- 1184.2s I still don't try to make it perfect, just very roughly looking at me. Okay. Now, I go to the Y view. Now, in the Y view, you can see it's a top-down view and it shows us that we should be looking at the specimen facing down. So, now we focus on the green specifically until we're happy with it. Then, we go
- 1205.56s to the X view, which tells us that the specimen should be looking to the left. Here, I'm going to focus then only on the red ring and I'm going to align it until I see a position where I would
- 1217.08s like in the future to crop the image. What do I mean with this? Is if I were to crop the image from the bottom, would it include part of the bottom of the mouth here that I want to keep or not?
- 1229.88s So, for example, if I did this and then I crop the bottom, I would likely have to crop part of the mouth to get higher on the neck here. So, I like this one.
- 1240.04s And once I'm happy with this, I go back to Z, so the original, and I fix the final Z alignment. Okay, this looks pretty symmetrical to me.
- 1250.8s Now, I'm being more picky about it, fine-tuning it. I go to Y again. Y looks pretty good.
- 1259.36s And then X again, and X looks pretty good. Okay, so now I'm happy with it. Now, we can apply this rotation. You can either apply it to this scan alone or to all the scans. If you apply it to all the scans, essentially what you're saying is that all of the scans were already in the
- 1276.16s same position. That is not the case here as you saw. Every scan is in a different position.
- 1281.48s So, I'm going to only apply it to this scan. You shouldn't need to touch the interpolation method. It will show you that if you change it to nearest neighbor, that is only necessary for binary images. That is if you were applying this rotation for a
- 1296.24s segmentation mask, for example. So, we keep it as linear, and then we just hit apply the scan, and then we just wait. Okay, so now that it finished, we can go ahead and click on it. One thing you can notice is that now on the same bundle, now you have this
- 1311.72s little icon here that says 3D rotated. This will be indicating each edit that you do to any of the images. This literally means that a new file got created that has the 3D rotation integrated on it.
- 1324.2s As you see now, the mesh is nicely aligned, but also the voxel image down here is very nicely aligned.
Storage awareness
Keywords: tutorial, transcript, video, youtube, storage awareness
Summary
Each edit writes a new file—storage grows with cohort size and pipeline depth—so monitor disk usage early on large projects.
Algorithm / details
You have to be careful about a storage because every time you do an edit, a new file is created. So, if you go here, you can see that we're occupying 11 GB out of 1.3 TB. You might say, "Oh, this is plenty for what we need." But once you start running larger projects like 150, 200 images, then each step multiplies. Okay,
Related: tutorial-ch12, tutorial-ch14
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1334s · chapter 22:14
Tutorial transcript
- 1334.92s You have to be careful about a storage because every time you do an edit, a new file is created.
- 1342.16s So, if you go here, you can see that we're occupying 11 GB out of 1.3 TB. You might say, "Oh, this is plenty for what we need." But once you start running larger projects like 150, 200 images, then each step multiplies. Okay,
Set cropping manually
Keywords: tutorial, transcript, video, youtube, set cropping manually
Summary
Crop the reference in crop mode with axis sliders, mesh-overlap cues, optional auto-fit, and padding so subjects have room to align; apply and verify the new bounds.
Algorithm / details
for the next step, we're going to do some croppings. So, if we look at the image right now, there's some extra space that we don't need. Maybe there's some part of the anatomy that we just don't want. And for that, you come to the preview where the mesh is, and you'll see these icons on the right side. The third one says crop mode. We click on that. What we see here is the actual volume containing the image representing is in this red box. And then, on the left side, we see a little window with some options. So, let's go into full screen and walk through each of these. First of all, we have these sliders that change the cropping. The way to think about this is when we move the red slider, the red lines will shrink, okay? So, we move the red slider, red lines shrink. Something you can notice here is that every time the boundaries of the box cross the mesh, the parts of the mesh that are crossing the box will be painted in a different color. So, you see whatever is going to be inside and what's going to be outside of your image. And you'll get a warning here that says how many vertices are being left out, just in case it's an accident and you actually didn't mean to remove anything from it. Uh, you'll also see from which voxel to which voxel on each axis you are going to be doing the cropping. We have an option here that is glowing. And this button here that's called auto fit crop to mesh. When you click this, it will automatically give you the smallest bounding box that contains the mesh. If your image is noisy and you have floating specs all around, this is not going to work very well because then it's just going to contain everything including those floating specs. Another option you have here is show all scans. Um, it's not going to work right now because show all scans only works when all of your scans are the same exact size. And what it does is it loads them all together and shows them to you on top of each other. This way you can crop to a bounding box that contains all of your scans combined. Now, since we are cropping the reference, but we need a room for all the other scans to align to this one, then we need to add a padding. By default, the padding is 40 voxels. However, if you see a lot of size variation in your data, you probably will want this padding to be larger. Since my embryos here are not that variable in size, I'm just going to use 40. This essentially adds 40 voxels on each side of each axis of the box. Okay, so here for example, I did the smallest bounding box, but now I do not want the bottom of the neck. So, I can go below here and align to the x-axis, and then shrink the green line, as you can see here. I still want the mouth, so I'm just going to crop right around there. Okay, and now that we have that, we can probably get away with shrinking a little bit more of the red. Perfect. So, what this is going to do is it's going to crop it first to this exact volume, then it will add padding with values that are assigned as background, which in this case will be zero. So, once you're ready and you like this, we hit apply to scan, and we wait. Okay, so now we have the image cropped to that size. We can verify by going again into the crop mode, and now we see the red box, which has the same cropping that we established, plus the 40 voxels on each side. We can also see that the cropping got applied to the actual voxel image. Okay, now
Related: tutorial-ch13, tutorial-ch15
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1360s · chapter 22:40
Tutorial transcript
- 1360.52s for the next step, we're going to do some croppings. So, if we look at the image right now, there's some extra space that we don't need. Maybe there's some part of the anatomy that we just don't want.
- 1371.68s And for that, you come to the preview where the mesh is, and you'll see these icons on the right side. The third one says crop mode. We click on that.
- 1382.92s What we see here is the actual volume containing the image representing is in this red box. And then, on the left side, we see a little window with some options.
- 1394.24s So, let's go into full screen and walk through each of these. First of all, we have these sliders that change the cropping.
- 1402.2s The way to think about this is when we move the red slider, the red lines will shrink, okay?
- 1411.64s So, we move the red slider, red lines shrink. Something you can notice here is that every time the boundaries of the box cross the mesh, the parts of the mesh that are crossing the box will be painted in a different color. So, you see whatever is going to be inside and what's going to be outside of your
- 1430.56s image. And you'll get a warning here that says how many vertices are being left out, just in case it's an accident and you actually didn't mean to remove anything from it. Uh, you'll also see from which voxel to which voxel on each axis you are going to be doing the cropping. We have an option here that is
- 1449.04s glowing. And this button here that's called auto fit crop to mesh. When you click this, it will automatically give you the smallest bounding box that contains the mesh.
- 1461.4s If your image is noisy and you have floating specs all around, this is not going to work very well because then it's just going to contain everything including those floating specs. Another option you have here is
- 1473.84s show all scans. Um, it's not going to work right now because show all scans only works when all of your scans are the same exact size.
- 1486.0s And what it does is it loads them all together and shows them to you on top of each other. This way you can crop to a bounding box that contains all of your scans combined. Now, since we are cropping the reference, but we need a room for all the other scans to align to this one,
- 1505.84s then we need to add a padding. By default, the padding is 40 voxels. However, if you see a lot of size variation in your data, you probably will want this padding to be larger.
- 1517.96s Since my embryos here are not that variable in size, I'm just going to use 40. This essentially adds 40 voxels on each side of each axis of the box. Okay, so here for example, I did the smallest bounding box, but now I do not want the bottom of the neck. So, I can go below here and align to the x-axis, and
- 1542.44s then shrink the green line, as you can see here. I still want the mouth, so I'm just going to crop right around there. Okay, and now that we have that, we can probably get away with shrinking a
- 1556.0s little bit more of the red. Perfect. So, what this is going to do is it's going to crop it first to this exact volume, then it will add padding with values that are assigned as background, which in this case will be zero. So, once you're ready and you like
- 1592.68s this, we hit apply to scan, and we wait. Okay, so now we have the image cropped to that size. We can verify by going again into the crop mode, and now we see the red box, which has the same cropping that we established, plus the 40 voxels on each side. We can also see that the cropping got applied
- 1613.44s to the actual voxel image. Okay, now
Background offset correction manually
Keywords: tutorial, transcript, video, youtube, background offset correction manually
Summary
Set background intensity on the reference (from the crosshair or batch auto mode using the thresholded background mode) so background voxels go to zero and the histogram/threshold shift accordingly.
Algorithm / details
that we have the rotation and the cropping and you can see them right here as the edits. You can also see them on the bundle. The next step for our reference image is usually to establish the background value. So, if we again click and drag our crosshair on the image here, we see that the values of the background are around 12,000 as seen on the top left here. So, if we want to just establish the background value for this image, we go down on the bottom right here on the settings icon and we find there's an option here called set background. By default, it will pick up that value that the crosshair is at and it will suggest it here as the background. We can just go ahead and click apply. And we wait. Okay, now that it's done, we can see that it reloaded and we have the image with the background all set to zero. In the histogram, we can see it because it's the largest peak over here. What happened is that all the intensity values got shifted to the left. So, the same number was subtracted from every single voxel and the threshold got adjusted in the same way. Another thing you can do um is to set the background instead of manually just doing it for this one scan, you can go up here and there's this option here that says set background value. Here you can as well just set one number, but for all the images together or you can set the background automatically by clicking this yellow button. The way the automatic mode works is using the same threshold you establish here, it will only take the values that are belonging to the background according to this threshold and then it will calculate the mode, the most frequent number, and it will use that as the background value. By the way, everything is explained in detail if you go into here and then hover over the buttons, it will just explain what everything does. Another step you can do from the
Related: tutorial-ch14, tutorial-ch16
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1615s · chapter 26:55
Tutorial transcript
- 1616.04s that we have the rotation and the cropping and you can see them right here as the edits. You can also see them on the bundle. The next step for our reference image is usually to establish the background value. So, if we again click and drag our crosshair on the image here, we see that the values of
- 1636.08s the background are around 12,000 as seen on the top left here. So, if we want to just establish the background value for this image, we go down on the bottom right here on the settings icon and we find there's an option here called set
- 1653.6s background. By default, it will pick up that value that the crosshair is at and it will suggest it here as the background.
- 1662.48s We can just go ahead and click apply. And we wait. Okay, now that it's done, we can see that it reloaded and we have the image with the background all set to zero.
- 1674.8s In the histogram, we can see it because it's the largest peak over here. What happened is that all the intensity values got shifted to the left. So, the same number was subtracted from every single voxel and the threshold got adjusted in the same way. Another thing you can do
- 1692.72s um is to set the background instead of manually just doing it for this one scan, you can go up here and there's this option here that says set background value.
- 1703.84s Here you can as well just set one number, but for all the images together or you can set the background automatically by clicking this yellow button. The way the automatic mode works is using the same threshold you establish here, it will only take the values that are
- 1722.8s belonging to the background according to this threshold and then it will calculate the mode, the most frequent number, and it will use that as the background value. By the way, everything is explained in detail if you go into here and then hover over the buttons, it
- 1740.64s will just explain what everything does. Another step you can do from the
Set N4 bias correction (for MRI)
Keywords: tutorial, transcript, video, youtube, set n4 bias correction (for mri)
Summary
Use N4 bias correction mainly for MRI when slow intensity gradients make one side of the volume dimmer; run it on the current scan or the whole cohort.
Algorithm / details
settings below here is the N4 bias correction. This is usually necessary only for MRI scans where you have this low-frequency bands of intensity differences. So, if one side of the image looks dimmer than the other side, it's probably this type of bias that you can correct with this option. Now, if you click on this, it will only do it for this image, but if you go ahead and click the one on top here, it will do it for all the scans together. One more step I will suggest to do to
Related: tutorial-ch15, tutorial-ch17
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1743s · chapter 29:03
Tutorial transcript
- 1744.88s settings below here is the N4 bias correction. This is usually necessary only for MRI scans where you have this low-frequency bands of intensity differences.
- 1757.56s So, if one side of the image looks dimmer than the other side, it's probably this type of bias that you can correct with this option.
- 1766.92s Now, if you click on this, it will only do it for this image, but if you go ahead and click the one on top here, it will do it for all the scans together.
- 1775.64s One more step I will suggest to do to
Set intensity normalization manually (percentiles and zoom-in tool)
Keywords: tutorial, transcript, video, youtube, set intensity normalization manually (percentiles and zoom-in tool)
Summary
Normalize the reference with percentile stretching (preferred over histogram matching or peak alignment for most cases), choosing high/low percentiles from the zoomed histogram preview.
Algorithm / details
the reference here will be the intensity normalization. As you can see, most of the intensity values right now are on the lower end of your available range of data. So, if we move the slider down, you see that the range gets better and better until we start to see some clipping into black. Okay. So, what I do is I move the slider until I don't see significant clipping, which will be somewhere around there. The only clipping I see now is little pieces of the tongue. By the way, if you want to zoom in, hold the control key and then click and slide. You'll see a zoomed version. Okay, we see this area has some clipping. It's not significant unless you're going to study the tongue, then you would be clipping values of the tongue, then you would probably want to be safer, go up here, let's say right there. So, I think I'm going to do this one. So, what we see now is 99.96% be the top end, and then if we go up here to the intensity normalization options, first I want to only do the scan, so I toggle this that says normalize only current scan. There's many options to do this. Um one is histogram matching. I do not recommend to use this unless your images are very equal to each other, same size, same orientation almost, and same distribution of intensity values. The other one that you could do is align intensity peaks. It finds what are the peaks of the distribution and then tries to shift them until they match. Again, same observation from the one before. I would only do it if they more or less align. Then we have tissue aware normalization. This one tries to find what the peak is and then assume that everything above that peak is going to be bone and then everything below will be soft tissue and then it will try to do both separately. The one in yellow here, percentile normalization, this is the one I suggest doing most of the times. This is going to do a very decent job. What this is going to do is find the percentiles and then stretch all the intensity values to fit the full range. So we go ahead and click it and we have the low percentile as 2%. That's fine since all the values from the background are lumped at the beginning, 2% is almost zero. Then for the high end we should do the same one we found here, 99.96. We can just click here and manually set it. Perfect. Now we click apply normalization and we wait. Okay, so what
Related: tutorial-ch16
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1776s · chapter 29:36
Tutorial transcript
- 1777.72s the reference here will be the intensity normalization. As you can see, most of the intensity values right now are on the lower end of your available range of data. So, if we move the slider down, you see that the range gets better and better until we
- 1795.32s start to see some clipping into black. Okay. So, what I do is I move the slider until I don't see significant clipping, which will be somewhere around there. The only clipping I see now is little pieces of the tongue.
- 1812.52s By the way, if you want to zoom in, hold the control key and then click and slide.
- 1818.52s You'll see a zoomed version. Okay, we see this area has some clipping. It's not significant unless you're going to study the tongue, then you would be clipping values of the tongue, then you would probably want to be safer, go up here, let's say right there. So, I think I'm going to do this one. So, what we
- 1836.64s see now is 99.96% be the top end, and then if we go up here to the intensity normalization options, first I want to only do the scan, so I toggle this that says normalize only current scan. There's many options to do
- 1851.52s this. Um one is histogram matching. I do not recommend to use this unless your images are very equal to each other, same size, same orientation almost, and same distribution of intensity values. The other one that you could do is align intensity peaks. It finds what are the peaks of the
- 1872.92s distribution and then tries to shift them until they match. Again, same observation from the one before. I would only do it if they more or less align.
- 1882.12s Then we have tissue aware normalization. This one tries to find what the peak is and then assume that everything above that peak is going to be bone and then everything below will be soft tissue and
- 1894.56s then it will try to do both separately. The one in yellow here, percentile normalization, this is the one I suggest doing most of the times. This is going to do a very decent job. What this is going to do is find the percentiles and then stretch all the intensity values to
- 1910.88s fit the full range. So we go ahead and click it and we have the low percentile as 2%. That's fine since all the values from the background are lumped at the beginning, 2% is almost zero. Then for the high end we should do the same one we found here,
- 1929.96s 99.96. We can just click here and manually set it. Perfect. Now we click apply normalization and we wait. Okay, so what
Semi-automated batch processing of the rest of the scans
Rigid alignment from scan to reference (resampling - rotation - cropping)
Keywords: tutorial, transcript, video, youtube, rigid alignment from scan to reference (resampling - rotation - cropping)
Summary
With a refined reference and raw subjects, the core value of Aurora is semi-automatically matching subjects to that reference—starting with rigid alignment.
Algorithm / details
do we have so far? So far we have a very highly refined reference scan and then two other scans that still have no edits done to them. So if we look at it on the grid view, you can see the reference scan is very nicely processed, oriented and everything, but the other two scans still in their original state. So, here is the biggest value proposition for this software, which is now that you have this reference, you should be able to semi-automatically make the other scans match what the reference looks like. So, the first thing that we need to do for that is alignment.
Related: tutorial-ch19
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1939s · chapter 32:19
Tutorial transcript
- 1939.44s do we have so far? So far we have a very highly refined reference scan and then two other scans that still have no edits done to them. So if we look at it on the grid view, you can see the reference scan is very nicely processed, oriented and everything, but the other two scans
- 1956.56s still in their original state. So, here is the biggest value proposition for this software, which is now that you have this reference, you should be able to semi-automatically make the other scans match what the
- 1970.12s reference looks like. So, the first thing that we need to do for that is alignment.
ALPACA alignment (failed attempt and debugging)
Keywords: tutorial, transcript, video, youtube, alpaca alignment (failed attempt and debugging)
Summary
Try ALPACA rigid align first (mesh-based, with automatic resampling); when it fails on hard cases, inspect extracted/.../debug alignment figures (PCA, RANSAC, ICP) to see where it broke.
Algorithm / details
So, if we go up here to the rigid align to reference, we will see three options. The first one's called Alpaca. It is the one that we mostly recommend to try initially because it's automated and it relies only on the meshes of the images. Therefore, you should be making sure that the threshold is correct so that the meshes of all the scans are good. Once you make sure they're nice, you can go ahead and click Alpaca. One thing to note is that if the voxel sizes, what I name here the spatial resolution, is not the exact same for all the scans. Uh here I just show three decimal places, but in reality, it is very likely that your scans come with many more. What the rigid align to reference step will do is it will actually try to match them by doing a resampling of the voxel space. If we go to the terminal here, we can actually trace this step. So, if we see here, it says voxel size of subject is 0.116, but the reference is 0.118. That means we need to do a resampling of the scan data by a factor of 0.98, etc. So, this is done automatically. You don't need to worry about it. Um it's just nice for me to explain that this is happening. This becomes very important for the later steps that do the elastic registration and all these different things that we want to do in a homogeneous space. Okay, now that it's done, we can go in and check the results. If we click on the scan, here now we can see that it didn't do a good job and that is expected since the embryos are more difficult to register using this Alpaca method. One way that you can check what happened behind the scenes is you can go to the folder where your scans are. You're going to notice this folder called extracted. This folder got generated the moment you ran the extract all scans. Inside this folder, there's everything that happens on the software and it is nicely kept uh separate from your raw scans so that nothing modifies them. Inside here, we can go to one of the scans that we just tried to align and we're going to see a folder called debug alignment. If we go in here, we see some uh figures that got generated for every step of the alignment process. We see the initial cloud of points from the two scans when they were separate from each other. Then the first step is they are centered to their centroids and normalized. Then we see that PCA runs where the main components of variation try to align between the two. Then we do a RANSAC. Uh the RANSAC takes a randomized subset of the points and tries to minimize the distance between the two. Then once that's done, we do three levels of ICP, iterative closest point. First with a low resolution, then a medium resolution, then a high resolution. We see here that it failed all the way every step from the PCA to the RANSAC to the ICP. When we load the grid view, we can see that it failed in both cases. Reason it fails is the RANSAC distributes all the points on the meshes evenly. However, the reality is that for this to succeed, they would need to weigh in the areas that have more idiosyncratic features, more defined anatomy that we can actually match between the two, and weigh less areas that look smooth. Cuz in this case, for example, you have more points or landmarks placed in the smooth areas of the neck than, let's say, here close to the face that is more defined. So, what
Related: tutorial-ch18, tutorial-ch20
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=1978s · chapter 32:58
Tutorial transcript
- 1978.2s So, if we go up here to the rigid align to reference, we will see three options.
- 1983.28s The first one's called Alpaca. It is the one that we mostly recommend to try initially because it's automated and it relies only on the meshes of the images. Therefore, you should be making sure that the threshold is correct so that the meshes of all the scans are good. Once you make sure they're nice,
- 2003.92s you can go ahead and click Alpaca. One thing to note is that if the voxel sizes, what I name here the spatial resolution, is not the exact same for all the scans. Uh here I just show three decimal places, but in reality, it is very likely that your scans come with many more. What the rigid align to
- 2026.08s reference step will do is it will actually try to match them by doing a resampling of the voxel space. If we go to the terminal here, we can actually trace this step. So, if we see here, it says voxel size of subject is 0.116, but the
- 2046.04s reference is 0.118. That means we need to do a resampling of the scan data by a factor of 0.98, etc.
- 2055.88s So, this is done automatically. You don't need to worry about it. Um it's just nice for me to explain that this is happening. This becomes very important for the later steps that do the elastic registration and all these different things that we want to do in a homogeneous space. Okay, now that it's
- 2073.92s done, we can go in and check the results. If we click on the scan, here now we can see that it didn't do a good job and that is expected since the embryos are more difficult to register using this Alpaca method. One way that you can check what happened behind the scenes is you can go to the folder where
- 2094.56s your scans are. You're going to notice this folder called extracted. This folder got generated the moment you ran the extract all scans.
- 2104.2s Inside this folder, there's everything that happens on the software and it is nicely kept uh separate from your raw scans so that nothing modifies them. Inside here, we can go to one of the scans that we just tried to align and we're going to see a folder called debug alignment. If we go
- 2122.44s in here, we see some uh figures that got generated for every step of the alignment process.
- 2130.6s We see the initial cloud of points from the two scans when they were separate from each other. Then the first step is they are centered to their centroids and normalized.
- 2141.4s Then we see that PCA runs where the main components of variation try to align between the two. Then we do a RANSAC. Uh the RANSAC takes a randomized subset of the points and tries to minimize the distance between the two. Then once that's done, we do three levels of ICP,
- 2161.88s iterative closest point. First with a low resolution, then a medium resolution, then a high resolution.
- 2168.56s We see here that it failed all the way every step from the PCA to the RANSAC to the ICP. When we load the grid view, we can see that it failed in both cases.
- 2179.24s Reason it fails is the RANSAC distributes all the points on the meshes evenly. However, the reality is that for this to succeed, they would need to weigh in the areas that have more idiosyncratic features, more defined anatomy that we can actually match between the two, and weigh less areas
- 2200.48s that look smooth. Cuz in this case, for example, you have more points or landmarks placed in the smooth areas of the neck than, let's say, here close to the face that is more defined. So, what
Delete edits
Keywords: tutorial, transcript, video, youtube, delete edits
Summary
Remove failed alignment edits from grid view or the selection trash UI so you can retry another rigid method without leftover transforms.
Algorithm / details
you can do now is you can go ahead and delete those edits. Uh there's multiple ways to do this. The first one would be here in grid view, you just click on the trash icon on the right here. Once you click on that, all the edits will appear below each of the scans. Then you can click click, you see how it goes red. That means those edits got selected. Um if you close the grid view, you'll see that there's an analogous a system here where you can check this box here, and then it automatically enables you to select or unselect things that you want to delete. You can select all, select none. Once you're ready to delete them, you just click on the trash bin over here and click okay. Okay, so now we're going to
Related: tutorial-ch19, tutorial-ch21
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2212s · chapter 36:52
Tutorial transcript
- 2213.08s you can do now is you can go ahead and delete those edits. Uh there's multiple ways to do this. The first one would be here in grid view, you just click on the trash icon on the right here. Once you click on that, all the edits will appear below each of the scans. Then you can click click, you see
- 2231.08s how it goes red. That means those edits got selected. Um if you close the grid view, you'll see that there's an analogous a system here where you can check this box here, and then it automatically enables you to select or unselect things that you want
- 2247.76s to delete. You can select all, select none. Once you're ready to delete them, you just click on the trash bin over here and click okay. Okay, so now we're going to
ANTs rigid alignment (failed attempt)
Keywords: tutorial, transcript, video, youtube, ants rigid alignment (failed attempt)
Summary
ANTs rigid alignment registers intensity volumes instead of meshes; it can also fail when subjects start far from the reference orientation.
Algorithm / details
try the second approach. We're going to try ANTS. ANTS will try to do the registration on the intensity volume images themselves. Okay, now it's finished, and we will check, and we see that it also failed for this method, and that is expected since the images themselves were very out of position between each other, and this makes it harder for them to align with just ANTS. So, we can go ahead and delete them, and now we can go and use
Related: tutorial-ch20, tutorial-ch22
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2256s · chapter 37:36
Tutorial transcript
- 2257.04s try the second approach. We're going to try ANTS. ANTS will try to do the registration on the intensity volume images themselves.
- 2267.36s Okay, now it's finished, and we will check, and we see that it also failed for this method, and that is expected since the images themselves were very out of position between each other, and this makes it harder for them to align with just ANTS. So, we can go ahead and delete them, and now we can go and use
Manual guidepoint-based alignment
Keywords: tutorial, transcript, video, youtube, manual guidepoint-based alignment
Summary
When automated rigid methods fail, place homologous guide points (start on the reference) and run guidepoint-based alignment for the most accurate rigid match.
Algorithm / details
the last method, which is manual guide points. To use manual guide points, we need uh first to set up the guide points on each of the images. This is a process that takes some time, but it's worth it because it will make it the most accurate. For most cases, Alpaca will do a good job. This is one of the cases where it doesn't. So, here I'm going to show you how to set up the guide points. Let's start first with the reference scan. That is very important that you start with this one, and I'll show you why in a second. So, first we're going to go on the right side here, and we're going to find this icon that says place guide points or landmarks. We go in, and we see these two options. We go and click on guide points. Once this mode is active, we can go into full screen to get a better view of this. And now we're simply just going to find places that will have homologous locations in each of the scans. For example, the tip of the nose. That is a very identifiable location in every single scan. And now we're going to tap G on the keyboard, and that will place the guide point right where the mouse is. And you can see on the left here that it got added to a list. In this list, we can delete them, reorder them, etc. Now, let's go ahead and put more guide points. It is important that you put at least three, but the more the better. What you want is to select enough locations that all together align the images on the places that you want. For example, if we place all our guide points near the back of the head, then all the images would align to the back, but probably the front would be misaligned. Conversely, in this case, I'm putting all of them mostly in the front, one in the top, which will make a lot of the anatomy in the back to misalign. But we don't care about that because our final goal will be to add landmarks to the whole face, so we don't really care to analyze the back here. We're focused on the shape of this face. Okay, so now we click save. And now you will see that there's an icon here that says this scan has guide points. Now, what you want to do is go ahead and place guide points in the other two. And since you already placed the guide points in the first one, the process will be guided. What I mean by guided is that you might see here that all the previous guide points that we place here appear from the reference and they're grayed out. So, here what we're going to do is we're going to hold the R key on our keyboard and this will show us the reference. So, here's the reference and we see that the first highlighted guide point is the tip of the nose. So, then we let go of the R key and then we can just go and find that tip of the nose here and then type G. Now, if we wait, automatically it will show us the next guide point. The next guide point being the bottom lip. We place it, automatically it will show us what the next one is. That's the right eye. Okay, so now we go to the right eye. I guess it's it's left eye. Okay, now this is the right eye. Perfect. Now, we go here and then click on there. Now, the top of the head. Perfect. We go to the top of the head. Excellent. So, effectively now we have the same guide points in this specimen. Now, we can just click save and then we can move to the next subject. Okay? So, now we click on the next one. Same, we go to guide points and it will load them from the reference. And if we already memorized where all the guide points are supposed to go, one thing that we can do is go up here to settings in the working directory. You see this icon. There's an option here that says show next guide point preview. We can toggle that off and that will now make it so that we can just place the guide points without it showing us which one's the next. Cuz let's say we already memorized that it's this one, this one, This one, this one. This way you can just go faster. Obviously, if you have more guide points and you are still not familiar with the order, then you should leave that toggled on. Perfect. Now, everything has guide points. Now, we can go ahead and click on the manual guide points alignment and just wait. Okay, now it finished processing. So, now we're going to look at the results. Here we can see that it's successfully aligned the three images. At this point,
Related: tutorial-ch21, tutorial-ch23
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2286s · chapter 38:06
Tutorial transcript
- 2287.56s the last method, which is manual guide points. To use manual guide points, we need uh first to set up the guide points on each of the images. This is a process that takes some time, but it's worth it because it will make it the most accurate. For most cases, Alpaca will do
- 2304.0s a good job. This is one of the cases where it doesn't. So, here I'm going to show you how to set up the guide points. Let's start first with the reference scan. That is very important that you start with this
- 2316.76s one, and I'll show you why in a second. So, first we're going to go on the right side here, and we're going to find this icon that says place guide points or landmarks.
- 2326.96s We go in, and we see these two options. We go and click on guide points. Once this mode is active, we can go into full screen to get a better view of this. And now we're simply just going to find places that will have homologous locations in each of the scans. For example, the tip of the nose. That is a
- 2346.2s very identifiable location in every single scan. And now we're going to tap G on the keyboard, and that will place the guide point right where the mouse is. And you can see on the left here that it got added to a list.
- 2360.04s In this list, we can delete them, reorder them, etc. Now, let's go ahead and put more guide points. It is important that you put at least three, but the more the better. What you want is to select enough locations that all together align the images on the places that you want. For example, if we place
- 2379.4s all our guide points near the back of the head, then all the images would align to the back, but probably the front would be misaligned. Conversely, in this case, I'm putting all of them mostly in the front, one in the top, which will make a lot of the anatomy in the back to misalign. But we
- 2397.28s don't care about that because our final goal will be to add landmarks to the whole face, so we don't really care to analyze the back here. We're focused on the shape of this face. Okay, so now we click save. And now you will see that there's an icon here that says this scan
- 2415.04s has guide points. Now, what you want to do is go ahead and place guide points in the other two. And since you already placed the guide points in the first one, the process will be guided. What I mean by guided is that you might see here that all the previous guide points that we place here appear from the
- 2432.64s reference and they're grayed out. So, here what we're going to do is we're going to hold the R key on our keyboard and this will show us the reference. So, here's the reference and we see that the first highlighted guide point is the tip
- 2446.48s of the nose. So, then we let go of the R key and then we can just go and find that tip of the nose here and then type G.
- 2455.16s Now, if we wait, automatically it will show us the next guide point. The next guide point being the bottom lip. We place it, automatically it will show us what the next one is. That's the right eye.
- 2468.08s Okay, so now we go to the right eye. I guess it's it's left eye. Okay, now this is the right eye.
- 2477.0s Perfect. Now, we go here and then click on there. Now, the top of the head. Perfect. We go to the top of the head. Excellent.
- 2488.52s So, effectively now we have the same guide points in this specimen. Now, we can just click save and then we can move to the next subject. Okay? So, now we click on the next one. Same, we go to guide points and it will load them from the reference. And if we already memorized where all the guide points are
- 2506.48s supposed to go, one thing that we can do is go up here to settings in the working directory. You see this icon.
- 2513.64s There's an option here that says show next guide point preview. We can toggle that off and that will now make it so that we can just place the guide points without it showing us which one's the next.
- 2527.52s Cuz let's say we already memorized that it's this one, this one, This one, this one. This way you can just go faster. Obviously, if you have more guide points and you are still not familiar with the order, then you should leave that toggled on. Perfect. Now, everything has guide points. Now, we can
- 2545.08s go ahead and click on the manual guide points alignment and just wait. Okay, now it finished processing. So, now we're going to look at the results.
- 2557.12s Here we can see that it's successfully aligned the three images. At this point,
Background offset correction automatically
Keywords: tutorial, transcript, video, youtube, background offset correction automatically
Summary
Once subjects match the reference in size, spacing, and orientation, run automatic background offset on the remaining scans so intensity floors line up.
Algorithm / details
all images have the same dimensions, the same voxel size, the same orientation. In other words, they are spatially equivalent. Now, the only remaining things will be the intensity. For that, we can just follow the same steps. We saw that the first one is setting the background. So, we can just go ahead and click on set background automatically. This will automatically set the background for these two images, ignoring the reference, since the reference already has its own. Okay, now it finished and we can check the results quickly. And here we go, the background is all the same level. Now, for the next
Related: tutorial-ch22, tutorial-ch24
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2561s · chapter 42:41
Tutorial transcript
- 2561.96s all images have the same dimensions, the same voxel size, the same orientation. In other words, they are spatially equivalent.
- 2571.16s Now, the only remaining things will be the intensity. For that, we can just follow the same steps. We saw that the first one is setting the background. So, we can just go ahead and click on set background automatically.
- 2585.68s This will automatically set the background for these two images, ignoring the reference, since the reference already has its own. Okay, now it finished and we can check the results quickly. And here we go, the background is all the same level. Now, for the next
Set intensity normalization automatically (same percentile as reference)
Keywords: tutorial, transcript, video, youtube, set intensity normalization automatically (same percentile as reference)
Summary
Apply the same percentile normalization used on the reference to all subjects; atypical intensity distributions may need separate treatment.
Algorithm / details
step, we are going to do the intensity normalization for all of them. So, we go back here and we click percentile normalization again. And then, if you remember the reference scan, we did it with 99.96. So, we click set volume and then we apply the normalization. It's worth mentioning that one of the three scans here seems to have this blurry part in the center. I think that one will likely work different with the percentile normalization, because for that one, the distribution of intensity values will be different. Therefore, uh 2% and 99.96% will be an actual different value with respect to the others. It's not a big big problem. It's just something to keep in mind. If you see this a lot in your scans, maybe it'll be worth it to treat those ones separately. But again, I don't think is a great great issue. Okay, now it's done. Let's check the results. It looks pretty good. Like I said, this specimen has pretty blurry inside of the head, but since we're just going to use the outside for this purpose, um it should work still. Okay, now if we go
Related: tutorial-ch23, tutorial-ch25
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2599s · chapter 43:19
Tutorial transcript
- 2600.56s step, we are going to do the intensity normalization for all of them. So, we go back here and we click percentile normalization again.
- 2610.84s And then, if you remember the reference scan, we did it with 99.96. So, we click set volume and then we apply the normalization. It's worth mentioning that one of the three scans here seems to have this blurry part in the center. I think that one will likely work different with the percentile
- 2634.16s normalization, because for that one, the distribution of intensity values will be different. Therefore, uh 2% and 99.96% will be an actual different value with respect to the others.
- 2651.8s It's not a big big problem. It's just something to keep in mind. If you see this a lot in your scans, maybe it'll be worth it to treat those ones separately.
- 2663.12s But again, I don't think is a great great issue. Okay, now it's done. Let's check the results.
- 2670.4s It looks pretty good. Like I said, this specimen has pretty blurry inside of the head, but since we're just going to use the outside for this purpose, um it should work still. Okay, now if we go
Verify all scans are within bounds
Keywords: tutorial, transcript, video, youtube, verify all scans are within bounds
Summary
In crop mode, use Show all scans to overlay the cohort and confirm nothing sits outside the reference bounds before elastic registration.
Algorithm / details
into the reference, here's something you can do to see all the different scans on top of each other. You just go in, go into crop mode, and then click here on show all scans. And then just wait until all of them load. Here now they all loaded, and you can probably not see the the smaller ones that are just inside. But we can see that none of them are falling too far away from this center area. If you wanted to crop further, you can do that here, and then just apply to all scans. But that won't be necessary for this case. So, what is next? Before
Related: tutorial-ch24, tutorial-ch26
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2684s · chapter 44:44
Tutorial transcript
- 2685.56s into the reference, here's something you can do to see all the different scans on top of each other. You just go in, go into crop mode, and then click here on show all scans.
- 2696.4s And then just wait until all of them load. Here now they all loaded, and you can probably not see the the smaller ones that are just inside.
- 2706.04s But we can see that none of them are falling too far away from this center area. If you wanted to crop further, you can do that here, and then just apply to all scans. But that won't be necessary for this case. So, what is next? Before
Batch cleanup mesh (remove disconnected components)
Keywords: tutorial, transcript, video, youtube, batch cleanup mesh (remove disconnected components)
Summary
Before elastic registration, batch-cleanup meshes to drop disconnected noise (optional intermediate Gaussian for speckles without permanently blurring saved images).
Algorithm / details
we do the most important step, which is the elastic registration, one thing I like to do right before is to run a batch cleanup mesh. The batch cleanup mesh will remove anything that is disconnected from the largest component, in this case the head. So, if you had a noisy patches, things that are leftover anatomy that is just floating away, but it's separate from the main region of interest, then this will collect them and dispose them. Furthermore, if you apply a Gaussian blur, here for example, I can set up 0.8, then what this is going to do is it's just going to apply the Gaussian blur as an intermediate step, and then it's going to check what didn't make it through this filtering. And then, it's going to take that as something to exclude additionally, unless it is connected to the largest component. So, if you have a very noisy image, lots of speckles floating around, you will want to use Gaussian blur before the the batch cleanup mesh. One thing to note is that the Gaussian blur won't be applied to your image. That is to say, the image that is going to be saved as the next step won't be a blurred. It's just used as one of the intermediate steps. With that being said, these scans don't seem to have anything disconnected that's too serious that would affect our analysis. But, uh in any case, I'm just going to run it with a Gaussian blur of zero. Okay, now it's finished, and we can see that all of the scans got the mesh cleaned icon. That means that the algorithm found disconnected components. Otherwise, if it doesn't find anything, it won't create this new edit. You can quickly look at them. They look same as before. As you can see, this one's uh quite smaller than the reference. And then, this one, as we already discussed, has less details internally.
Related: tutorial-ch25, tutorial-ch27
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2718s · chapter 45:18
Tutorial transcript
- 2720.8s we do the most important step, which is the elastic registration, one thing I like to do right before is to run a batch cleanup mesh. The batch cleanup mesh will remove anything that is disconnected from the largest component,
- 2735.0s in this case the head. So, if you had a noisy patches, things that are leftover anatomy that is just floating away, but it's separate from the main region of interest, then this will collect them and dispose them.
- 2749.64s Furthermore, if you apply a Gaussian blur, here for example, I can set up 0.8, then what this is going to do is it's just going to apply the Gaussian blur as an intermediate step, and then it's going to check what didn't make it through this filtering. And then, it's going to take that as something to
- 2769.28s exclude additionally, unless it is connected to the largest component. So, if you have a very noisy image, lots of speckles floating around, you will want to use Gaussian blur before the the batch cleanup mesh. One thing to note is that the Gaussian blur won't be applied
- 2785.48s to your image. That is to say, the image that is going to be saved as the next step won't be a blurred. It's just used as one of the intermediate steps. With that being said, these scans don't seem to have anything disconnected that's too serious that would affect our analysis. But, uh
- 2805.16s in any case, I'm just going to run it with a Gaussian blur of zero. Okay, now it's finished, and we can see that all of the scans got the mesh cleaned icon.
- 2817.04s That means that the algorithm found disconnected components. Otherwise, if it doesn't find anything, it won't create this new edit. You can quickly look at them. They look same as before.
- 2830.16s As you can see, this one's uh quite smaller than the reference. And then, this one, as we already discussed, has less details internally.
Elastic registration options overview
Keywords: tutorial, transcript, video, youtube, elastic registration options overview
Summary
Configure elastic registration presets (intermediate → aggressive) with affine init, double pass, masking, and optional dice threshold adjustment—hover tooltips explain each control.
Algorithm / details
So, now the next step is going to be to elastically align everything to this reference. We can expect that these two cases, which are hard cases, will struggle at some point, but we will see how the results do. Okay, so for the elastic registration, we have this window here. We have many options. If you hover over the options, it tells you what parameters it is using for that registration. I highlight in yellow the one I suggest to do for most of the scenarios. If you run the intermediate and you find that it's still not very good, you can then try aggressive fast. And if that's not enough, you run the aggressive. Why should you go in order? Um because they take a lot of time. So, if time is not an issue for you and you have a powerful computer and you you can leave this running, you can start from a very aggressive registration. So, we have some options here below. The first one is affine initialization. As you can see, this is just using an affine transform that initially aligns everything together. You can scale, shear, and displace the image. It is very recommended that you run this always. Then we have the double pass option. The double pass essentially runs the same registration algorithm again after the first one is done. This is especially useful if certain parts of the image need to match others that are very far away in the other image. So, for example, the scan that we saw that is very small, that with the affine initialization, you would expect it to scale up and almost already match it initially. Then we have use mask. I always recommend using this one. This one uses the threshold you already set up for all the scans to create a mask. And then it will try to register only the elements within the mask and ignore anything in the background. Finally, we have dice adjustment. Dice adjustment will calculate the dice score coefficient after the registration is done, and then it will change the threshold until you get the maximum dice overlap. That is to say, this will change the threshold you already established until you find a better overlap between the two meshes. You can do this one if you realize that the intensity distributions are very different between each other and you want to almost make sure the same amount of content is thresholded. This could be risky though because if the registration is not perfect, then it will overdo this thresholding. So, I recommend leaving it off unless you have a specific case to use this for.
Related: tutorial-ch26, tutorial-ch28
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=2840s · chapter 47:20
Tutorial transcript
- 2840.68s So, now the next step is going to be to elastically align everything to this reference. We can expect that these two cases, which are hard cases, will struggle at some point, but we will see how the results do. Okay, so for the elastic registration, we have this
- 2862.84s window here. We have many options. If you hover over the options, it tells you what parameters it is using for that registration. I highlight in yellow the one I suggest to do for most of the scenarios.
- 2878.76s If you run the intermediate and you find that it's still not very good, you can then try aggressive fast. And if that's not enough, you run the aggressive. Why should you go in order?
- 2892.2s Um because they take a lot of time. So, if time is not an issue for you and you have a powerful computer and you you can leave this running, you can start from a very aggressive registration. So, we have some options here below. The
- 2905.96s first one is affine initialization. As you can see, this is just using an affine transform that initially aligns everything together. You can scale, shear, and displace the image. It is very recommended that you run this always. Then we have the double pass option. The double pass essentially runs
- 2929.32s the same registration algorithm again after the first one is done. This is especially useful if certain parts of the image need to match others that are very far away in the other image.
- 2943.96s So, for example, the scan that we saw that is very small, that with the affine initialization, you would expect it to scale up and almost already match it initially. Then we have use mask. I always recommend using this one. This one uses the threshold you already set
- 2959.4s up for all the scans to create a mask. And then it will try to register only the elements within the mask and ignore anything in the background. Finally, we have dice adjustment. Dice adjustment will calculate the dice score coefficient after the registration is done, and then it will change the
- 2980.4s threshold until you get the maximum dice overlap. That is to say, this will change the threshold you already established until you find a better overlap between the two meshes. You can do this one if you realize that the intensity distributions are very different between each other and you
- 3000.64s want to almost make sure the same amount of content is thresholded. This could be risky though because if the registration is not perfect, then it will overdo this thresholding. So, I recommend leaving it off unless you have a specific case to
- 3018.72s use this for.
A word of caution on double pass
Keywords: tutorial, transcript, video, youtube, a word of caution on double pass
Summary
Double pass can refine hard-to-reach anatomy but risks overfitting into unrealistic “liquefied” deformations—use it judiciously.
Algorithm / details
Then we have the double pass. The double pass allows us to run the same registration again on the output of the first one. What this can help us with is refining details that sometimes are too far away or not very easy to align. However, this runs the risk of us over-fitting the two images, uh which results in uh liquefied quality. It's almost as if one image is really forcing itself to try to match the other, which is not realistic. Okay, so the elastic
Related: tutorial-ch27, tutorial-ch29
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=3020s · chapter 50:20
Tutorial transcript
- 3020.08s Then we have the double pass. The double pass allows us to run the same registration again on the output of the first one. What this can help us with is refining details that sometimes are too
- 3033.2s far away or not very easy to align. However, this runs the risk of us over-fitting the two images, uh which results in uh liquefied quality. It's almost as if one image is really forcing itself to try to match the other, which is not realistic. Okay, so the elastic
Reviewing elastic registration results (understanding metrics | bad outcomes)
Keywords: tutorial, transcript, video, youtube, reviewing elastic registration results (understanding metrics | bad outcomes)
Summary
Interpret weak elastic results via surface-proximity % and Dice, per-subject error heatmaps, and reference-averaged registration-error tools (including landmark-colored error).
Algorithm / details
registration finished and I decided to do two things. I ran it with poor settings that gave me these bad outcomes and then I'm going to show you the results with better settings. I essentially chose a very fast registration with no double pass, no affine. And as you can see, the performance was poor. So, here we get a percentage. If we hover over the alert here, you can see that for this specific subject, there was 67% alignment of the surface. This is measured by how many vertices from the whole mesh of the outer surface was less than six voxels away from the reference. In this one, we see it's 67%. I set up the threshold of 90% to show you this icon of an alert. Other than that, we also calculate the dice score, which essentially shows you how much of the total area overlaps between the two after registration. But this includes everything internal to the mesh. So, if you're interested in every single part of the image aligning, this is a good one, the dice score. But if you're interested in only the outer surface, then the first one is better. In this one, both of them performed poorly. If you prefer to see the dice score here, you can go to settings and then just switch here to dice and you'll see the result of the dice. We can see that the second one had a nicer dice score, but still not a very good surface distance. Not too bad, though. 86% is not the worst. You have to probably manually check them to verify this. If we click in one of the subjects, we will see the registration error as a heat map on the mesh. So, here you can see that the distance from this mesh to the reference was very poor in many places. Right now, the heat map scale is from 0 mm to 0.04 mm, which is the equivalent to 0 to three voxels. If we want to see a wider range, all we need to do is click on the color bar on top here. If we click it once, now we see that we're looking at 0 to about six voxels away. And the millimeter conversion will depend on the voxel sizing of your image. But the number of voxels I show in the bar are always the same. So, here we see it at six, so we click again. 0 now to 12, and then we can click again 0 to 24 and then we're back to 0 to 3. Okay, now what if we have hundreds of images and we don't want to go and click on all of them and see how the registration worked on each of them. What we do then is we go to the reference scan. Now we need to go to the place guide points or landmarks mode which is here on the right side of the 3D mesh. Here we see the landmarks option. We click on that and here we have some options on top. The first one says registration error with a button that refreshes and then a switch that if we toggle it on we'll be able to see what is essentially the average of all the registration errors from all the scans. So now that it finished loading we can see the average error. And same as before we can click on the color bar to circle through the different ranges and furthermore what we can do here is we can place our cursor somewhere in the mesh and then tap the key on your keyboard H. When you do that you will automatically load a histogram of what that specific area in the mesh looks like for all the subjects. So since we have only two subjects we only see the two here and we see that both of them specifically where I clicked are 0.5 and 0.4 voxels away. Automatically what happens here is an area of vertices are sampled and then an average distance is calculated. So by default this area is 0.5 voxels but you can change here how big you want that area to be. The other thing you can do is just use a single point. So if instead you just want the single vertex where the cursor over the mesh was, you just change to single, click update, and then you'll see the more specific values. I do not recommend doing single unless you have a very particular scenario just because the meshes can be noisy sometimes. And an individual vertex on the mesh won't represent the actual distance. But if you do an area and it's a small enough area, it will average out some vertices and it will tell you exactly how many. For example, here it's telling me it grabbed 49 vertices and that's what is being averaged. Now that you're here in the histogram, if you click on the bars, it will highlight down here which subject that value belongs to. By default, they are ordered by the largest distance to the smallest distance. So, on top you'll see the worst ones and then on the bottom you'll see the best ones. And the color that they follow will be the exact same color as your current color bar range over here on the top. Now if we go into one of these red areas, we can go ahead and again tap H and here we can see for example that only one of them is very far away, which is the subject. The other one seems fine. So then what you can do is you just click on that subject and it will automatically open it on the preview so you can analyze what went wrong. And here we go and we already saw this one. We can see pretty clearly those areas are of high error. Okay, let's go back to that reference scan and now let's go back to the landmark placement. We go into landmarks and then we load the registration heat map again and since we already had it calculated before, it will load from memory. You can see it loaded pretty fast. If we made some changes and we want to reload it or look at it with a higher detail mesh, then you just go and click this icon here that refreshes the whole calculation. Okay, so now what else can we do here? There is this second switch over here that says landmark registration error. What this is is a tool that colors every landmark that we place on the mesh by this mean registration on a radius around the area. Okay, so now what I'm going to do is I'm going to toggle off the registration heat map. Then I'm going to place some landmarks by using the G key on the keyboard. We can see these landmarks are just appearing in the mesh as red spheres. If we then turn on the registration heat map again, and then we turn on the landmark registration error mode here, now these landmarks get that color of what the registration error is at those areas. If we are confused because they are blending with the heat map in the background, we can just toggle off the registration heat map here, and you can still see them with the [clears throat] color that was assigned. Essentially, this helps us do landmark placement that tries to avoid areas of high error. So, if we turn this back on, we would try to avoid this, for example, and we would try to place landmarks over here. Although, this is just an example, and this level of error is too much. Obviously, this wouldn't be good for you to work with. Okay, now for the more
Related: tutorial-ch28, tutorial-ch30
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=3052s · chapter 50:52
Tutorial transcript
- 3053.6s registration finished and I decided to do two things. I ran it with poor settings that gave me these bad outcomes and then I'm going to show you the results with better settings. I essentially chose a very fast registration with no double pass,
- 3074.48s no affine. And as you can see, the performance was poor. So, here we get a percentage.
- 3082.56s If we hover over the alert here, you can see that for this specific subject, there was 67% alignment of the surface. This is measured by how many vertices from the whole mesh of the outer surface was less
- 3096.76s than six voxels away from the reference. In this one, we see it's 67%. I set up the threshold of 90% to show you this icon of an alert. Other than that, we also calculate the dice score, which essentially shows you how much of the total area overlaps between the two after registration. But this includes
- 3122.16s everything internal to the mesh. So, if you're interested in every single part of the image aligning, this is a good one, the dice score. But if you're interested in only the outer surface, then the first one is better. In this
- 3140.4s one, both of them performed poorly. If you prefer to see the dice score here, you can go to settings and then just switch here to dice and you'll see the result of the dice.
- 3150.76s We can see that the second one had a nicer dice score, but still not a very good surface distance. Not too bad, though. 86% is not the worst.
- 3161.64s You have to probably manually check them to verify this. If we click in one of the subjects, we will see the registration error as a heat map on the mesh. So, here you can see that the distance from this mesh to the reference was very poor in many places. Right now, the heat map scale is from 0 mm to 0.04
- 3184.52s mm, which is the equivalent to 0 to three voxels. If we want to see a wider range, all we need to do is click on the color bar on top here.
- 3197.32s If we click it once, now we see that we're looking at 0 to about six voxels away.
- 3204.64s And the millimeter conversion will depend on the voxel sizing of your image. But the number of voxels I show in the bar are always the same. So, here we see it at six, so we click again.
- 3219.96s 0 now to 12, and then we can click again 0 to 24 and then we're back to 0 to 3. Okay, now what if we have hundreds of images and we don't want to go and click on all of them and see how the
- 3234.28s registration worked on each of them. What we do then is we go to the reference scan. Now we need to go to the place guide points or landmarks mode which is here on the right side of the 3D mesh. Here we see the landmarks option. We click on that and here we
- 3252.92s have some options on top. The first one says registration error with a button that refreshes and then a switch that if we toggle it on we'll be able to see what is essentially the average of all the registration errors from all the scans. So now that it finished loading we can see the average
- 3273.0s error. And same as before we can click on the color bar to circle through the different ranges and furthermore what we can do here is we can place our cursor somewhere in the mesh and then tap the key on your keyboard H.
- 3292.72s When you do that you will automatically load a histogram of what that specific area in the mesh looks like for all the subjects.
- 3302.84s So since we have only two subjects we only see the two here and we see that both of them specifically where I clicked are 0.5 and 0.4 voxels away.
- 3315.36s Automatically what happens here is an area of vertices are sampled and then an average distance is calculated. So by default this area is 0.5 voxels but you can change here how big you want that area to be.
- 3331.08s The other thing you can do is just use a single point. So if instead you just want the single vertex where the cursor over the mesh was, you just change to single, click update, and then you'll see the more
- 3344.08s specific values. I do not recommend doing single unless you have a very particular scenario just because the meshes can be noisy sometimes. And an individual vertex on the mesh won't represent the actual distance.
- 3360.24s But if you do an area and it's a small enough area, it will average out some vertices and it will tell you exactly how many. For example, here it's telling me it grabbed 49 vertices and that's what is being averaged. Now that you're here in the histogram, if you click on
- 3376.96s the bars, it will highlight down here which subject that value belongs to. By default, they are ordered by the largest distance to the smallest distance.
- 3387.64s So, on top you'll see the worst ones and then on the bottom you'll see the best ones.
- 3392.52s And the color that they follow will be the exact same color as your current color bar range over here on the top.
- 3400.44s Now if we go into one of these red areas, we can go ahead and again tap H and here we can see for example that only one of them is very far away, which is the subject. The other one seems fine.
- 3414.16s So then what you can do is you just click on that subject and it will automatically open it on the preview so you can analyze what went wrong. And here we go and we already saw this one.
- 3425.52s We can see pretty clearly those areas are of high error. Okay, let's go back to that reference scan and now let's go back to the landmark placement. We go into landmarks and then we load the registration heat map again and since we already had it calculated before, it will load from memory. You can see it
- 3444.76s loaded pretty fast. If we made some changes and we want to reload it or look at it with a higher detail mesh, then you just go and click this icon here that refreshes the whole calculation. Okay, so now what else can
- 3459.52s we do here? There is this second switch over here that says landmark registration error.
- 3466.16s What this is is a tool that colors every landmark that we place on the mesh by this mean registration on a radius around the area. Okay, so now what I'm going to do is I'm going to toggle off the registration heat map. Then I'm going to place some landmarks by using the G key on the keyboard. We can see
- 3485.04s these landmarks are just appearing in the mesh as red spheres. If we then turn on the registration heat map again, and then we turn on the landmark registration error mode here, now these landmarks get that color of what the registration error is at those areas. If we are confused because they are
- 3508.88s blending with the heat map in the background, we can just toggle off the registration heat map here, and you can still see them with the [clears throat] color that was assigned. Essentially, this helps us do landmark placement that tries to avoid areas of high error. So, if we turn this back on,
- 3526.68s we would try to avoid this, for example, and we would try to place landmarks over here. Although, this is just an example, and this level of error is too much.
- 3538.24s Obviously, this wouldn't be good for you to work with. Okay, now for the more
Reviewing elastic registration results (better settings and successful outcomes)
Keywords: tutorial, transcript, video, youtube, reviewing elastic registration results (better settings and successful outcomes)
Summary
Stronger settings (fine init, double pass, mask) markedly improve proximity and Dice; inspect heatmaps and high-detail meshes to confirm residual error is mostly mesh noise.
Algorithm / details
aggressive elastic registration using a fine initialization and double pass and mask, we see a very drastic difference. Now we have a 95% proximity rate to the surface of the reference, a dice coefficient of 83% for the first subject. Then for the second subject, we have 91% proximity and 90% dice score. So, that's pretty decent. And now, let's go ahead and look at it qualitatively. All right, here we have the first subject and it looks pretty decent. As we can see, most of the values lie around less than one voxel away, which is pretty good. Now, if we want to see more detail, we can go ahead and load the high-detail mesh. Now, we can see that the heat map got recalculated with much more details and the areas that seem to have higher error, we can see that they are actually mostly due to the noisy nature of the mesh. Now, we can go
Related: tutorial-ch29
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=3541s · chapter 59:01
Tutorial transcript
- 3542.32s aggressive elastic registration using a fine initialization and double pass and mask, we see a very drastic difference.
- 3552.2s Now we have a 95% proximity rate to the surface of the reference, a dice coefficient of 83% for the first subject. Then for the second subject, we have 91% proximity and 90% dice score.
- 3567.56s So, that's pretty decent. And now, let's go ahead and look at it qualitatively. All right, here we have the first subject and it looks pretty decent. As we can see, most of the values lie around less than one voxel away, which is pretty good. Now, if we want to see more detail, we can go ahead and load
- 3587.88s the high-detail mesh. Now, we can see that the heat map got recalculated with much more details and the areas that seem to have higher error, we can see that they are actually mostly due to the noisy nature of the mesh. Now, we can go
Annotating and propagating landmarks to the whole dataset
Options for landmarking
Keywords: tutorial, transcript, video, youtube, options for landmarking
Summary
Landmark either on the reference and propagate via elastic transforms, or work through an atlas—both paths live under the landmark transfer options.
Algorithm / details
ahead and start placing some landmarks. There's two options here. We can either go to the reference and place the landmarks there, which then we can just transfer by propagating them using the learned elastic registrations to the subjects. And that is all under this drop-down menu here that says transfer landmarks. Second option we have is to do it with
Related: tutorial-ch32
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=3603s · chapter 01:00:03
Tutorial transcript
- 3603.88s ahead and start placing some landmarks. There's two options here. We can either go to the reference and place the landmarks there, which then we can just transfer by propagating them using the learned elastic registrations to the
- 3620.24s subjects. And that is all under this drop-down menu here that says transfer landmarks.
- 3627.68s Second option we have is to do it with
Create an Atlas
Keywords: tutorial, transcript, video, youtube, create an atlas
Summary
Create an atlas by averaging deformation fields then intensities, yielding a shape-preserving mean image for landmarking when the reference is less ideal.
Algorithm / details
an atlas. What happens here is that when we create atlas, I'm going to click it now, it's going to process each of the transformations and generate the average image that actually represents the shape of the average between all of these. So, it finds the average deformation field, essentially bringing each subject to the average position. And then, once all of the scans are in this average position, then we do the intensity averaging. Great. So, now the atlas finished compiling. Now, we can go and click on it. Here, we can see it's obviously just crafted out of these three scans, so it's not going to be a perfect average, but it's a pretty decent-looking one. And we can see the anatomical features are well preserved thanks to this approach, so it's not too blurry. So, what I'm going to do now is I'm going to
Related: tutorial-ch31, tutorial-ch33
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=3628s · chapter 01:00:28
Tutorial transcript
- 3630.6s an atlas. What happens here is that when we create atlas, I'm going to click it now, it's going to process each of the transformations and generate the average image that actually represents the shape of the average between all of these. So, it finds the average deformation field,
- 3650.72s essentially bringing each subject to the average position. And then, once all of the scans are in this average position, then we do the intensity averaging.
- 3660.6s Great. So, now the atlas finished compiling. Now, we can go and click on it. Here, we can see it's obviously just crafted out of these three scans, so it's not going to be a perfect average,
- 3671.44s but it's a pretty decent-looking one. And we can see the anatomical features are well preserved thanks to this approach, so it's not too blurry. So, what I'm going to do now is I'm going to
Placing landmarks on the reference scan
Keywords: tutorial, transcript, video, youtube, placing landmarks on the reference scan
Summary
Prefer placing landmarks on the reference (fewer hops than atlas→reference→subjects) while watching registration-error heatmaps; use G to place points and semi-landmarks along mesh geodesics.
Algorithm / details
place landmarks in the reference scan. The reason I'm going to do this is it's one less step to transfer landmarks from the reference to subjects. If your reference is decent enough that you can see the features that you want to place landmarks in, then it's better to just use this one. Otherwise, the Atlas needs to internally first transfer the landmarks to a reference and then from reference to the subjects. So, that extra degree of separation could add some slight errors, although nothing to be very concerned about. The other reason I want to use the reference to put the landmarks on is that as we showed before, you can visualize the registration error as you do it. So, here we go. I'm going back to the landmarking system here. I'm going to recalculate the registration heat map cuz this kept the old one where we had those poorly performing registration. Great. So, now that it finished reloading, we can now start placing the landmarks using the G key on our keyboard. For example, here I'm just going to start placing some areas that I know would be of interest here. For example, tip of the nose, bottom lip. And now I want to show you something interesting here, which is let's say I want to place a series of landmarks between two existing landmarks. So, we can click on this one, drag it to the semi landmarks block here, and then click the next one, drag it to the second point here, and then we can just establish how many we want between them. So, here I want to eight semi landmarks, and I just click apply, and then we wait. What this is doing is it is finding the shortest path that follows the outer surface contour of the mesh that connects those two landmarks. Here they are and you can see they are colored differently. They are in blue as opposed to the main landmarks which are in red and we can keep placing landmarks now. So that's it. This is a rough example of what you can achieve. This is obviously not a very good landmarking set. I just made it up for the purposes of this video. Obviously, your expertise in the anatomical literature will guide your placement of the landmarks here. So what I'm going to do now is I'm going to check the landmark registration error. So we're going to turn on registration heat map again. Then I'm going to turn the landmark registration error. Then I'm going to turn off the heat map so I can see them more clearly. And we can see that only a few of them appear not to be completely blue, but they also don't look severely bad in the current range. So I'm happy with how this looks. So now what I will do is click save here. Now that it's done, we can see this logo here on the right side of the bundle that says 98%. What this means is that 98% of the landmarks we just placed are accurately close to the surface in the high-resolution mesh. This is a very sensitive calculation I make here just to keep track of how good the landmarks are positioned. So what we want to do now is we want to make sure they are really snug against the high resolution mesh. To do this, we go into landmark mode, which is distinct from the landmark placement mode. And here we can see each of the individual landmarks and in a different color how far they are from the surface. By the way, if you are color blind, you can just come up here to the settings and then turn on this color blind accessible landmark palette and this will change from green and red to blue, pink, and yellow. So, what we want to do here now is we're going to snap all this to the mesh and for that we have this option the bottom here that says snap landmarks to mesh. Some other options we have in here are random landmarking, which will just place landmarks randomly trying to distribute them evenly on the surface. However, they won't be anatomically paired to any features. Smart landmarking is still under development. Then you can download the mesh, you can download the landmarks, and you can also upload landmarks if you have existing ones. Now that it finished, we can see that now it says 100% on the landmarks in the bundle, but we see that previously it was 98%. So, it keeps both numbers just as a reference. If we go back here to landmark mode, we can see now all of them look color blue, which is zero. And if we switch the color palette, they're all green in here. And if we are curious to see how far they snap from, we can just turn turn on this option that's called snap distance and we can see again some of them look a little more yellow. Going to go to the other palette just so you see, some of them are more magenta. Perfect. Great. So, now that they have been snapped, we can go ahead and
Related: tutorial-ch32, tutorial-ch34
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=3682s · chapter 01:01:22
Tutorial transcript
- 3683.92s place landmarks in the reference scan. The reason I'm going to do this is it's one less step to transfer landmarks from the reference to subjects. If your reference is decent enough that you can see the features that you want to place landmarks in, then it's better to just
- 3701.2s use this one. Otherwise, the Atlas needs to internally first transfer the landmarks to a reference and then from reference to the subjects.
- 3710.76s So, that extra degree of separation could add some slight errors, although nothing to be very concerned about. The other reason I want to use the reference to put the landmarks on is that as we showed before, you can visualize the
- 3725.16s registration error as you do it. So, here we go. I'm going back to the landmarking system here. I'm going to recalculate the registration heat map cuz this kept the old one where we had
- 3740.12s those poorly performing registration. Great. So, now that it finished reloading, we can now start placing the landmarks using the G key on our keyboard. For example, here I'm just going to start placing some areas that I know would be of interest here. For
- 3755.4s example, tip of the nose, bottom lip. And now I want to show you something interesting here, which is let's say I want to place a series of landmarks between two existing landmarks. So, we can click on this one, drag it to the semi landmarks block here, and then click the next one, drag it to the
- 3779.68s second point here, and then we can just establish how many we want between them.
- 3784.04s So, here I want to eight semi landmarks, and I just click apply, and then we wait. What this is doing is it is finding the shortest path that follows the outer surface contour of the mesh that connects those two landmarks. Here they are and you can see they are colored differently. They are in blue as
- 3803.6s opposed to the main landmarks which are in red and we can keep placing landmarks now.
- 3831.24s So that's it. This is a rough example of what you can achieve. This is obviously not a very good landmarking set. I just made it up for the purposes of this video. Obviously, your expertise in the anatomical literature will guide your
- 3847.48s placement of the landmarks here. So what I'm going to do now is I'm going to check the landmark registration error.
- 3855.56s So we're going to turn on registration heat map again. Then I'm going to turn the landmark registration error. Then I'm going to turn off the heat map so I can see them more clearly. And we can see that only a few of them appear not to be completely blue, but they also don't look severely bad in the current
- 3874.36s range. So I'm happy with how this looks. So now what I will do is click save here. Now that it's done, we can see this logo here on the right side of the bundle that says 98%. What this means is that 98% of the landmarks we just placed are accurately close to the surface in
- 3897.0s the high-resolution mesh. This is a very sensitive calculation I make here just to keep track of how good the landmarks are positioned. So what we want to do now is we want to make sure they are really snug against the high resolution mesh. To do this, we go into landmark mode, which is distinct from the
- 3919.8s landmark placement mode. And here we can see each of the individual landmarks and in a different color how far they are from the surface.
- 3930.12s By the way, if you are color blind, you can just come up here to the settings and then turn on this color blind accessible landmark palette and this will change from green and red to blue, pink, and yellow. So, what we want to do here now is we're going to snap all this to the mesh and for that we have this
- 3948.72s option the bottom here that says snap landmarks to mesh. Some other options we have in here are random landmarking, which will just place landmarks randomly trying to distribute them evenly on the surface. However, they won't be anatomically
- 3965.04s paired to any features. Smart landmarking is still under development. Then you can download the mesh, you can download the landmarks, and you can also upload landmarks if you have existing ones. Now that it finished, we can see that now it says 100% on the landmarks in the bundle, but
- 3983.12s we see that previously it was 98%. So, it keeps both numbers just as a reference. If we go back here to landmark mode, we can see now all of them look color blue, which is zero. And if we switch the color palette, they're all green in here. And if we are curious to see how far they snap from, we can
- 4004.52s just turn turn on this option that's called snap distance and we can see again some of them look a little more yellow.
- 4011.68s Going to go to the other palette just so you see, some of them are more magenta.
- 4017.24s Perfect. Great. So, now that they have been snapped, we can go ahead and
Transfer landmarks from reference to subjects
Keywords: tutorial, transcript, video, youtube, transfer landmarks from reference to subjects
Summary
Transfer reference landmarks to subjects with snap-to-mesh and the learned elastic warps, then review placement quality on each subject.
Algorithm / details
transfer them to all our subjects. To do this, we have this option up here. It's called transfer landmarks, and we will toggle on the option parameter called snap landmarks to mesh. This way automatically after they are transferred, they will snap to the surface. And now we're going to run reference to subject. So now what we found here is that the first subject performed pretty decently. So now what we find here with the results is originally the first subject performed at a 72% rate, and it says here when we hover that 15 landmarks were displaced more than one voxel. So this percentage represents the success rate, but after the snapping we see that it's now at 100%. For the second subject, this was 87%. Now, the landmarks were transferred to the edit one before the elastic registration. This means that the marks are placed in this one, which is the third edit called mesh cleaned. So as you can see, we are at the edit number three now, and it has a little icon on the bottom that indicates that this is the one that has the landmarks. Now, if we go into landmark mode, we're able to see all the landmarks, and they appear to all be color blue, which means they are all tight against the mesh. If we click on snap distance, as we saw before, we can see how far each of the landmarks had to snap from. So we see there's a couple that are here on top that snapped from a distance around two voxels, which is not too bad. Now, something you might want to check is imagine that your data set is composed of 100 subjects, and you just transferred landmarks to all of them, and you don't want to click one by one to check if any of the landmarks snapped from way too far away. Here we can go um back to the landmark mode. Here we click on snap distance and then we click on show average from all, which is this option right beside the snap distance. And now you can see that the color changed. So these colors now represent the average distance that each of the landmarks had to snap from. Let's change the range from this to four voxels, okay? That's pretty good. If you are concerned about any of the landmarks, for example, let's say this one over here seems to be one of the highest distances. So we just click on it and it will display a histogram of that specific landmark for all the different subjects. So we can see here that in this subject the snapping distance was 1.64 voxels away, which is pretty good. And then for the other one was just one voxel. This can help you spot outliers, subjects that maybe somewhere along the way something didn't perform as well and it evaded all the checks before. And now at this stage, which is the data that you are actually interested in, which is the landmarks, will have the necessary information to spot this outliers. Finally, what we want to do
Related: tutorial-ch33, tutorial-ch35
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4020s · chapter 01:07:00
Tutorial transcript
- 4022.24s transfer them to all our subjects. To do this, we have this option up here. It's called transfer landmarks, and we will toggle on the option parameter called snap landmarks to mesh.
- 4036.2s This way automatically after they are transferred, they will snap to the surface.
- 4042.32s And now we're going to run reference to subject. So now what we found here is that the first subject performed pretty decently. So now what we find here with the results is originally the first subject performed at a 72% rate, and it says here when we hover that 15 landmarks were displaced more than one
- 4063.76s voxel. So this percentage represents the success rate, but after the snapping we see that it's now at 100%.
- 4073.76s For the second subject, this was 87%. Now, the landmarks were transferred to the edit one before the elastic registration. This means that the marks are placed in this one, which is the third edit called mesh cleaned. So as you can see, we are at the edit number three now, and it has a little icon on
- 4095.72s the bottom that indicates that this is the one that has the landmarks. Now, if we go into landmark mode, we're able to see all the landmarks, and they appear to all be color blue, which means they are all tight against the mesh. If we click on snap distance, as we saw before, we can see how far each of the
- 4114.32s landmarks had to snap from. So we see there's a couple that are here on top that snapped from a distance around two voxels, which is not too bad.
- 4123.76s Now, something you might want to check is imagine that your data set is composed of 100 subjects, and you just transferred landmarks to all of them, and you don't want to click one by one to check if any of the landmarks snapped from way too far away. Here we can go um back to the landmark mode. Here we
- 4143.56s click on snap distance and then we click on show average from all, which is this option right beside the snap distance.
- 4150.84s And now you can see that the color changed. So these colors now represent the average distance that each of the landmarks had to snap from. Let's change the range from this to four voxels, okay?
- 4164.96s That's pretty good. If you are concerned about any of the landmarks, for example, let's say this one over here seems to be one of the highest distances. So we just click on it and it will display a histogram of that specific landmark for all the different subjects. So we can
- 4183.04s see here that in this subject the snapping distance was 1.64 voxels away, which is pretty good.
- 4190.96s And then for the other one was just one voxel. This can help you spot outliers, subjects that maybe somewhere along the way something didn't perform as well and it evaded all the checks before.
- 4205.2s And now at this stage, which is the data that you are actually interested in, which is the landmarks, will have the necessary information to spot this outliers. Finally, what we want to do
Export all landmarks as a .csv file
Keywords: tutorial, transcript, video, youtube, export all landmarks as a .csv file
Summary
Export all landmarks as a CSV for Excel or downstream morphometrics; files are written into the project’s organized output structure.
Algorithm / details
here is export all the landmarks as a CSV file. So this is a table that you can open with Excel or any other program that can read CSVs. So what to you do is you can just click on this option here and it will download a zip folder. When we open this folder, we can see three CSVs got generated. The important one is this one that says landmarks. Other than that, we have two CSVs that end with the word distances. Each of these ones will show you how far each landmark is from the surface mesh. This one shows the current one. If they were snapped, you will expect this one to be close to zero for every single landmark. And for this one, you will see all of them pre-snapping. You can get the raw information of how far they were. So, the same things we were looking at here in the interface, now you can have them as data. This can help you generate statistical analysis that determine which parts of your landmarking schemes have more confidence or less. So, let's go ahead and open this CSV. So, here we are inside of Excel and we can see that we have the three subjects, reference and the other two. And we can see that for each landmark, there is X, Y, and Z. So, that is the format chosen here. You can obviously load this into any program of your choice. Now, we go to
Related: tutorial-ch34
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4216s · chapter 01:10:16
Tutorial transcript
- 4216.24s here is export all the landmarks as a CSV file. So this is a table that you can open with Excel or any other program that can read CSVs. So what to you do is you can just click on this option here and it will download a zip folder. When we open this folder, we can see three CSVs got
- 4237.84s generated. The important one is this one that says landmarks. Other than that, we have two CSVs that end with the word distances.
- 4248.4s Each of these ones will show you how far each landmark is from the surface mesh.
- 4253.48s This one shows the current one. If they were snapped, you will expect this one to be close to zero for every single landmark.
- 4261.32s And for this one, you will see all of them pre-snapping. You can get the raw information of how far they were.
- 4268.72s So, the same things we were looking at here in the interface, now you can have them as data.
- 4274.4s This can help you generate statistical analysis that determine which parts of your landmarking schemes have more confidence or less. So, let's go ahead and open this CSV. So, here we are inside of Excel and we can see that we have the three subjects, reference and
- 4293.44s the other two. And we can see that for each landmark, there is X, Y, and Z. So, that is the format chosen here.
- 4303.24s You can obviously load this into any program of your choice. Now, we go to
Segmentation module
Segmentation tools overview
Keywords: tutorial, transcript, video, youtube, segmentation tools overview
Summary
Open the segmentation module to paint masks on the reference, choose labels/brushes, and prepare tissue for AI fill, masking, and later landmarking on isolated anatomy.
Algorithm / details
the bottom here where we can see the intensity value images in the three views. And we can go to the bottom right and click on the wrench icon. This will show up some of the options available. The first option says segment mode. So, we click on this one and this will load the full suite that allows us to generate segmentations. By default, the image that is loaded it's the same as you see over here, which is a downscaled compressed version of the original image. So, if you wanted to work with the higher resolution image, all you need to do is click on this button over here that says full resolution. Perfect. So, now we can see the higher resolution image. Just a note, this takes a lot of memory and most computers won't be able to handle it. Only if you have a powerful computer, this is a useful way to look at your image. Otherwise, you will face some lagging and slow downs. In which case using the half resolution compressed image is just as okay. You just get less fine grain detail. So here I went back to the half resolution just to make things uh more efficient for the demo. So what you do here is first you can switch to threshold mode and in threshold mode you can move around the threshold to try to separate out the anatomy that you want to segment first. So one thing I want to do here is segment the brain. So I'm going to locate myself in a slice that contains as much brain as I can see for the current sagittal view. Now I want to move the threshold until it pretty much covers the area that I want to segment, but it feels like it's separate enough from surrounding anatomy. Right now to start I think the original one that we had works pretty decently. So now what you can see is the the brush here in red as a circle. We can change its size by using the scroll wheel. So we can scroll up and get a bigger size. We can see in the top left here it says brush size 85 pixels and then what
Related: tutorial-ch37
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4308s · chapter 01:11:48
Tutorial transcript
- 4309.04s the bottom here where we can see the intensity value images in the three views. And we can go to the bottom right and click on the wrench icon.
- 4321.12s This will show up some of the options available. The first option says segment mode. So, we click on this one and this will load the full suite that allows us to generate segmentations.
- 4335.6s By default, the image that is loaded it's the same as you see over here, which is a downscaled compressed version of the original image. So, if you wanted to work with the higher resolution image, all you need to do is click on this button over here that says full resolution. Perfect. So, now we can see
- 4356.24s the higher resolution image. Just a note, this takes a lot of memory and most computers won't be able to handle it. Only if you have a powerful computer, this is a useful way to look at your image. Otherwise, you will
- 4371.48s face some lagging and slow downs. In which case using the half resolution compressed image is just as okay. You just get less fine grain detail. So here I went back to the half resolution just to make things uh more efficient for the demo. So what you do here is first you can switch to threshold mode
- 4395.28s and in threshold mode you can move around the threshold to try to separate out the anatomy that you want to segment first. So one thing I want to do here is segment the brain.
- 4405.32s So I'm going to locate myself in a slice that contains as much brain as I can see for the current sagittal view. Now I want to move the threshold until it pretty much covers the area that I want to segment, but it feels like it's separate enough from surrounding anatomy. Right now to start I think the
- 4426.68s original one that we had works pretty decently. So now what you can see is the the brush here in red as a circle. We can change its size by using the scroll wheel.
- 4437.72s So we can scroll up and get a bigger size. We can see in the top left here it says brush size 85 pixels and then what
Manually segmenting a middle slice in the three axes views
Keywords: tutorial, transcript, video, youtube, manually segmenting a middle slice in the three axes views
Summary
Seed a 3D mask by painting mid-slices in all three axes with thresholded/regular brushes, eraser, undo, and optional 3D brush—then save before expanding with AI.
Algorithm / details
we can do is we can switch back to range mode so we can see the actual intensity values. And by default the option that is on is called thresholded brush. The thresholded brush as the name suggests will be a brush that if you paint through it will only keep the areas that are above the threshold. So we can do the bulk of this slice of the brain by filling up the inside without going into the outer edges yet. I'm going to do the bottom ones because those ones are well defined and nice. We can go down here. I can make the brush smaller. Now I can go into the finer details of the edges. If you have a tablet with a pen, it's also easier to use that. Now we can refine details here. So to refine details, we can just change to a regular brush. In the regular brush, we can just fill these gaps that appeared down here in the stem. And now let's say we screwed up and we painted outside of the boundaries. We can backtrack this mistake by clicking this button over here, undo. And finally here I'm going to use a razor and I'm going to go to a very small size and fix these details out here. Okay, so now I am happy with this slice. Now instead of going through all of the slices, which would take you forever as you can see here, there are above 300. What you can do is simply switch to the axial view. Here you can see it as a line. Now we can go ahead and fill up this other view. Okay, so now we go to the final view. Okay, so now that you're happy with the three views, what you need to do is go and click save mask. Once it is saved, now you can do a myriad of operations with it. Before I go into them, I want to show you some other options that we have here. First one is the 3D brush. So, when we turn it on, what it does is your brush can now paint across the slices. And the number of slices it can paint through will be equivalent to the same radius the brush is. So, for example, I'm going to paint here the tongue, and you will see that in the other views, if we align the crosshair here, you can see that it painted through. If I just paint one circle, the circle becomes a cylinder that paints through all the slices. I'm going to undo this. The 3D brush also works for the other brush options and eraser, for example. Something else you can do is you can apply the threshold completely to the whole image. So, if you do this, you will see that everything becomes masked that is above the threshold. So, this would be useful if you want to, instead of brush in, you want to erase out the mask that you're trying to get to. So, I'm going to undo this operation.
Related: tutorial-ch36, tutorial-ch38
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4446s · chapter 01:14:06
Tutorial transcript
- 4447.52s we can do is we can switch back to range mode so we can see the actual intensity values. And by default the option that is on is called thresholded brush.
- 4460.32s The thresholded brush as the name suggests will be a brush that if you paint through it will only keep the areas that are above the threshold.
- 4470.36s So we can do the bulk of this slice of the brain by filling up the inside without going into the outer edges yet.
- 4480.28s I'm going to do the bottom ones because those ones are well defined and nice. We can go down here.
- 4493.84s I can make the brush smaller. Now I can go into the finer details of the edges. If you have a tablet with a pen, it's also easier to use that. Now we can refine details here. So to refine details, we can just change to a regular brush. In the regular brush, we can just
- 4511.32s fill these gaps that appeared down here in the stem. And now let's say we screwed up and we painted outside of the boundaries. We can backtrack this mistake by clicking this button over here, undo.
- 4538.2s And finally here I'm going to use a razor and I'm going to go to a very small size and fix these details out here.
- 4567.4s Okay, so now I am happy with this slice. Now instead of going through all of the slices, which would take you forever as you can see here, there are above 300.
- 4578.0s What you can do is simply switch to the axial view. Here you can see it as a line. Now we can go ahead and fill up this other view.
- 4600.88s Okay, so now we go to the final view. Okay, so now that you're happy with the three views, what you need to do is go and click save mask. Once it is saved, now you can do a myriad of operations with it. Before I go into them, I want to show you some other options that we
- 4634.52s have here. First one is the 3D brush. So, when we turn it on, what it does is your brush can now paint across the slices. And the number of slices it can paint through will be equivalent to the same radius the brush is.
- 4652.04s So, for example, I'm going to paint here the tongue, and you will see that in the other views, if we align the crosshair here, you can see that it painted through.
- 4660.64s If I just paint one circle, the circle becomes a cylinder that paints through all the slices. I'm going to undo this.
- 4668.36s The 3D brush also works for the other brush options and eraser, for example. Something else you can do is you can apply the threshold completely to the whole image.
- 4679.84s So, if you do this, you will see that everything becomes masked that is above the threshold.
- 4685.96s So, this would be useful if you want to, instead of brush in, you want to erase out the mask that you're trying to get to. So, I'm going to undo this operation.
AI-based 3D segmentation (MedSAM2)
Keywords: tutorial, transcript, video, youtube, ai-based 3d segmentation (medsam2)
Summary
Run MedSAM2 to expand the three mid-slice seeds into a full volume via per-axis propagations and majority voting at each voxel.
Algorithm / details
And now, as you can see, we only segmented an intermediate slice for each of the three views. So, now if we want to get the full brain, one thing that we're going to do is we're going to use AI-based segmentation with a model that is called Net Sam 2. So, you can find that here in the right of the window. We choose the model and we just click run. This will use the mask that you saved previously and it will pass it through the AI algorithm. And what it does in a basic sense is go slice by slice and then expand out until it fills the volume. And that volume will be delimited by the extension of each of the three different view masks that already exist. In the end, this ends up becoming into three different segmentations of the whole brain. After which there is a voting system that on each voxel, it asks the question, are two or more of the segmentations including this voxel? If the answer is yes, then that will be included in the final segmentation. Now, we can see that the segmentation was loaded correctly in three dimensions. It performed pretty decently. There There might still be some inaccuracies here and there, but overall, it did a great
Related: tutorial-ch37, tutorial-ch39
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4696s · chapter 01:18:16
Tutorial transcript
- 4696.28s And now, as you can see, we only segmented an intermediate slice for each of the three views.
- 4704.2s So, now if we want to get the full brain, one thing that we're going to do is we're going to use AI-based segmentation with a model that is called Net Sam 2.
- 4716.04s So, you can find that here in the right of the window. We choose the model and we just click run. This will use the mask that you saved previously and it will pass it through the AI algorithm.
- 4730.68s And what it does in a basic sense is go slice by slice and then expand out until it fills the volume.
- 4739.24s And that volume will be delimited by the extension of each of the three different view masks that already exist. In the end, this ends up becoming into three different segmentations of the whole brain.
- 4754.28s After which there is a voting system that on each voxel, it asks the question, are two or more of the segmentations including this voxel? If the answer is yes, then that will be included in the final segmentation. Now, we can see that the segmentation was loaded correctly in three dimensions. It
- 4775.68s performed pretty decently. There There might still be some inaccuracies here and there, but overall, it did a great
Visualize the segmentation preview as a mesh
Keywords: tutorial, transcript, video, youtube, visualize the segmentation preview as a mesh
Summary
Preview the saved mask as a mesh (mask vs threshold display, optional blur), noting you must apply the mask to the image before landmarking on that tissue.
Algorithm / details
job. What you can do now is since this automatically saves, you can exit this window and visualize it separately. To do that, we need to refresh the website, go back to the reference. To visualize the created segmentation, we can just go into the refresh mode and within the option mask label, now we will see there is label one. If we select label one and then we wait, now what we see is a preview of what we just segmented using the three slices and then the AI approach. As you can see, this looks pretty decent. We can also see a preview of that in the voxel intensities down here. However, this is not applied to the edit. Now, if we want to smooth the mesh to prevent these jagged lines that we see from the segmentation. One thing we can do is go into the refresh mode. And now we see that currently it is set up to mask. So, what this does is the 3D mesh is created out of the mask and not the actual thresholded intensities. If we switch it to threshold and then refresh, now it it looks slightly smoother because now it's using the actual threshold for the area within the mask. And if we want to go further, we can just go down here and add a Gaussian blur, then click refresh. And now we have a very smooth surface that we can visualize and explore. But, let's say you want to add landmarks to this. So, you have this organ is the brain and then you want to study its shape, so you go to the landmarks mode, but now I show you this message here that says that to add landmarks, you must apply the mask to the image first. How do we do that? Okay. So, we go back to the segmentation
Related: tutorial-ch38, tutorial-ch40
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4783s · chapter 01:19:43
Tutorial transcript
- 4783.12s job. What you can do now is since this automatically saves, you can exit this window and visualize it separately. To do that, we need to refresh the website, go back to the reference. To visualize the created segmentation, we can just go into the refresh mode and within the
- 4802.44s option mask label, now we will see there is label one. If we select label one and then we wait, now what we see is a preview of what we just segmented using the three slices and then the AI approach.
- 4819.4s As you can see, this looks pretty decent. We can also see a preview of that in the voxel intensities down here.
- 4826.84s However, this is not applied to the edit. Now, if we want to smooth the mesh to prevent these jagged lines that we see from the segmentation. One thing we can do is go into the refresh mode.
- 4839.32s And now we see that currently it is set up to mask. So, what this does is the 3D mesh is created out of the mask and not the actual thresholded intensities.
- 4852.56s If we switch it to threshold and then refresh, now it it looks slightly smoother because now it's using the actual threshold for the area within the mask. And if we want to go further, we can just go down here and add a Gaussian blur, then click refresh. And now we have a very smooth surface that we can
- 4873.48s visualize and explore. But, let's say you want to add landmarks to this. So, you have this organ is the brain and then you want to study its shape, so you go to the landmarks mode, but now I show you this message here that says that to add landmarks, you must apply the mask
- 4890.6s to the image first. How do we do that? Okay. So, we go back to the segmentation
Additional manual segmentation options
Keywords: tutorial, transcript, video, youtube, additional manual segmentation options
Summary
Extra segmentation tools: invert threshold for negative-space cuts, add multiple labels, show all labels together, or delete unused labels.
Algorithm / details
mode. Now there's a shortcut to get there since there's a mask present and it's going to be this icon over here that says mask available for current edit. So, we just click on it. Now we're back here and before we do anything, I want to show you two more things about this interface. The first one is that we have an option that it is called invert threshold. So, if you go into threshold mode, you invert the threshold. Now you can create masks on the negative spaces. This can be useful to create specific cuts around objects that you can then clear out using the clean mesh option over here. So, this is a way to do it. Just go and cut up the negative spaces and then mask them out. Okay, the second thing I want to show you is you can add more labels. Right now, we just have this label one, but you can click in the plus icon over here, which adds a second label. So, now if we go back to the first label, it's right there, didn't move. We go to label two, we can now create a mask, let's say of, I don't know, the tongue or some other part of the head. And we can save this again, and now we would have the two labels to work with. Something you can do as well here is show all labels. So, if you want to see everything all together, it will take a different color, one for each object. And you can also fully delete the label. So, I can just delete the label two right here. And I'll click okay. And now we're back to this. Perfect. So, now to the masking. Two
Related: tutorial-ch39, tutorial-ch41
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4894s · chapter 01:21:34
Tutorial transcript
- 4896.04s mode. Now there's a shortcut to get there since there's a mask present and it's going to be this icon over here that says mask available for current edit.
- 4904.76s So, we just click on it. Now we're back here and before we do anything, I want to show you two more things about this interface. The first one is that we have an option that it is called invert threshold.
- 4917.44s So, if you go into threshold mode, you invert the threshold. Now you can create masks on the negative spaces.
- 4926.28s This can be useful to create specific cuts around objects that you can then clear out using the clean mesh option over here. So, this is a way to do it.
- 4938.56s Just go and cut up the negative spaces and then mask them out. Okay, the second thing I want to show you is you can add more labels. Right now, we just have this label one, but you can click in the plus icon over here, which adds a second label. So, now if we go back to the
- 4952.44s first label, it's right there, didn't move. We go to label two, we can now create a mask, let's say of, I don't know, the tongue or some other part of the head. And we can save this again, and now we would have the two labels to work with. Something you can do as well
- 4968.08s here is show all labels. So, if you want to see everything all together, it will take a different color, one for each object.
- 4975.52s And you can also fully delete the label. So, I can just delete the label two right here.
- 4982.8s And I'll click okay. And now we're back to this. Perfect. So, now to the masking. Two
Mask out or apply mask to image
Keywords: tutorial, transcript, video, youtube, mask out or apply mask to image
Summary
Mask out deletes voxels inside a label; Apply mask to image isolates tissue (e.g. brain) into a new masks folder scan that keeps prior transforms for landmark propagation.
Algorithm / details
options here. The first one is mask out label one. This will effectively create a new edit in which all the voxels within this mask are deleted. They will just be assigned a background value. This is what you would do if you were creating cuts between the organs, so that you can then clear them out using the clean mesh. The other thing you can do is that if you want to isolate the brain, for example, and put landmarks on it, there's this option here that says apply mask to image. This will save a masked scan. So, we can go ahead and click that. And this will prompt you to give a name. So, here I'm just going to type brain, and then click on apply and save. Once done, we can go ahead and close this and refresh the website. And now, what we can see is there's an option that appears up here that is called masked available. Behind the scenes, what happened is if we click this bundle option off, we can see that there are several folders in here. One of them is called masks. [clears throat] If we click on masks, we'll see there's brain. So, I'm just going to go back here. We can just click on this button and shortcut straight there. So, we'll go straight into this path that now says brain. Perfect. So, now we can click on this folder brain and we will see that this subject we clicked to mask out appears here. So, if we click on this we can see the extracted brain appears immediately. And now this is treated just as any other scan. So, now we can go ahead and look into placing landmarks and the benefit will be that this inherits all the elastic transformations and it inherits all the positioning and all the work we did before. So, if we apply the masks to all of the different subjects, we should be able to straight away place landmarks and propagate them. So, let's do that. Let's walk you through that process. So, before I even place landmarks, we need to get the rest of the subjects over here. How do we do that? Well, the same place where the button that appeared before that said available masks was now shows go to main folder. Now, we're back here. So, the first
Related: tutorial-ch40, tutorial-ch42
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=4987s · chapter 01:23:07
Tutorial transcript
- 4989.84s options here. The first one is mask out label one. This will effectively create a new edit in which all the voxels within this mask are deleted. They will just be assigned a background value. This is what you would do if you were creating cuts between the organs, so that you can then
- 5009.08s clear them out using the clean mesh. The other thing you can do is that if you want to isolate the brain, for example, and put landmarks on it, there's this option here that says apply mask to image.
- 5022.28s This will save a masked scan. So, we can go ahead and click that. And this will prompt you to give a name. So, here I'm just going to type brain, and then click on apply and save. Once done, we can go ahead and close this and refresh the website. And now, what we can see is there's an option that
- 5042.48s appears up here that is called masked available. Behind the scenes, what happened is if we click this bundle option off, we can see that there are several folders in here. One of them is called masks. [clears throat] If we click on masks, we'll see there's brain. So, I'm just going to go back
- 5059.96s here. We can just click on this button and shortcut straight there. So, we'll go straight into this path that now says brain. Perfect. So, now we can click on this folder brain and we will see that this subject we clicked to mask out appears here. So, if we click on this we can see the extracted brain
- 5083.96s appears immediately. And now this is treated just as any other scan. So, now we can go ahead and look into placing landmarks and the benefit will be that this inherits all the elastic transformations and it inherits all the positioning and all the work we did
- 5103.84s before. So, if we apply the masks to all of the different subjects, we should be able to straight away place landmarks and propagate them. So, let's do that. Let's walk you through that process. So, before I even place landmarks, we need to get the rest of the subjects over
- 5122.4s here. How do we do that? Well, the same place where the button that appeared before that said available masks was now shows go to main folder.
- 5133.2s Now, we're back here. So, the first
Transfer segmentation from reference to all subjects
Keywords: tutorial, transcript, video, youtube, transfer segmentation from reference to all subjects
Summary
Use Transform masks → reference mask to subjects to warp the reference segmentation across the cohort (optional per-subject, label, AI redo, or threshold).
Algorithm / details
thing we want to do is we want to transfer the existing mask on the reference to all our subjects. The way we do that is we go up here to this option that's called transform masks and then we can choose to only transfer it to a specific subject. We can also choose to only transfer a specific label. We can have the option to redo the AI process after it gets transferred and we can also choose to apply a threshold after it's transferred. By default, I recommend to not touch any of these and just go ahead and click on reference mask to subjects. Now, it reloaded. It finished transferring them. So, now we can go and do some quality checks. So now you can see that by default it gives you this mask available for current edit, and now we can also see it in the refresh options. We can choose the mask label one. It doesn't look very smooth, and as we can see in the image below, it probably is inaccurate. Now, this is one of the moments where you have to recognize that for tasks such as the segmentation of internal organs, the quality of the image impacts this a lot. And we knew this from the start. This subject had blurred out inside of the head, so it was problematic in that regard, even though the outside may have looked pretty decent and still works for landmark
Related: tutorial-ch41, tutorial-ch43
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5134s · chapter 01:25:34
Tutorial transcript
- 5135.36s thing we want to do is we want to transfer the existing mask on the reference to all our subjects.
- 5142.08s The way we do that is we go up here to this option that's called transform masks and then we can choose to only transfer it to a specific subject. We can also choose to only transfer a specific label. We can have the option to redo the AI process after it gets transferred and we can also choose to
- 5163.24s apply a threshold after it's transferred. By default, I recommend to not touch any of these and just go ahead and click on reference mask to subjects.
- 5173.08s Now, it reloaded. It finished transferring them. So, now we can go and do some quality checks.
- 5179.2s So now you can see that by default it gives you this mask available for current edit, and now we can also see it in the refresh options. We can choose the mask label one. It doesn't look very smooth, and as we can see in the image
- 5194.4s below, it probably is inaccurate. Now, this is one of the moments where you have to recognize that for tasks such as the segmentation of internal organs, the quality of the image impacts this a lot. And we knew this from the start. This subject had blurred out inside of the head, so it was
- 5215.88s problematic in that regard, even though the outside may have looked pretty decent and still works for landmark
Check the quality of the transferred segmentations
Keywords: tutorial, transcript, video, youtube, check the quality of the transferred segmentations
Summary
Visually QC transferred masks—internal contrast issues can degrade organ segmentations even when outer surfaces looked fine.
Algorithm / details
placements. So now we load it, and we can take a look at the results, and we can definitely see issues specifically in the back of the head here. It seems like the top was very decent. The bottom has some issues, and the back has some issues. Now, the other one looked very good on the inside, so it's less likely that this one will have big issues. So let's see what happens. Okay, so this is the mesh for that one, and this is the deformed brain. Let's see. So now let's take a look at the full mask. Okay, so now we are looking at this one, and to me it looks like it did a phenomenal job. Great. So now what we want to do, disregard the fact that this image number 50 had that issue that we observed,
Related: tutorial-ch42, tutorial-ch44
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5224s · chapter 01:27:04
Tutorial transcript
- 5224.08s placements. So now we load it, and we can take a look at the results, and we can definitely see issues specifically in the back of the head here. It seems like the top was very decent. The bottom has some issues, and the back has some issues. Now, the other one looked very
- 5241.8s good on the inside, so it's less likely that this one will have big issues. So let's see what happens. Okay, so this is the mesh for that one, and this is the deformed brain. Let's see. So now
- 5253.72s let's take a look at the full mask. Okay, so now we are looking at this one, and to me it looks like it did a phenomenal job. Great. So now what we want to do, disregard the fact that this image number 50 had that issue that we observed,
Apply the masks to all images
Keywords: tutorial, transcript, video, youtube, apply the masks to all images
Summary
Apply masks batch-wide for a chosen label and destination folder so every subject gets an isolated masked volume.
Algorithm / details
we now will apply the masks to all of them. So what what we're going to do is go into apply masks, this option over here. Select the label, which is label one, and then under mask folder, click on brain, and then apply all. So now that it finished, we go back here into mask available, into brain, and now we see the three of them.
Related: tutorial-ch43, tutorial-ch45
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5273s · chapter 01:27:53
Tutorial transcript
- 5273.24s we now will apply the masks to all of them. So what what we're going to do is go into apply masks, this option over here.
- 5282.36s Select the label, which is label one, and then under mask folder, click on brain, and then apply all. So now that it finished, we go back here into mask available, into brain, and now we see the three of them.
Place landmarks on the segmented tissue
Keywords: tutorial, transcript, video, youtube, place landmarks on the segmented tissue
Summary
On the masked tissue, place landmarks, save, and snap to mesh so points sit on the segmented surface before transfer.
Algorithm / details
Okay, so now we go into the place guide points landmarks mode, then we click on landmarks, and now we can go ahead and start placing some landmarks. And now that we're happy with it, we can just go ahead and click save. So now it's finished, and we can now check the performance of the landmarks. So we will go here and then check. Yeah, they seem to not be super well matched, but then we can just snap to mesh. Okay, now it's snapped. We can go and check. Yeah, now it everything looks like it is at zero, and we can now go ahead and transfer the
Related: tutorial-ch44, tutorial-ch46
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5294s · chapter 01:28:14
Tutorial transcript
- 5294.52s Okay, so now we go into the place guide points landmarks mode, then we click on landmarks, and now we can go ahead and start placing some landmarks.
- 5331.2s And now that we're happy with it, we can just go ahead and click save. So now it's finished, and we can now check the performance of the landmarks. So we will go here and then check. Yeah, they seem to not be super well matched, but then we can just snap to mesh. Okay, now it's
- 5349.76s snapped. We can go and check. Yeah, now it everything looks like it is at zero, and we can now go ahead and transfer the
Transfer the segmented tissue's landmarks to all subjects
Keywords: tutorial, transcript, video, youtube, transfer the segmented tissue's landmarks to all subjects
Summary
Propagate tissue landmarks to subjects, investigate snap outliers with right-click crosshair jumps, optionally redo AI after transfer, and export a tissue-specific CSV in the same space as surface landmarks.
Algorithm / details
landmarks. Same way we did before, we go and click on snap landmarks to mesh, and then click on reference to subjects. Now that it's finished, we can see that new values appear in the landmarking information. So we won't even go check the one that's problematic. I'm just going to go check the other one, even though it says it's snapped correctly 100% from 83%. It won't be representative of the actual anatomy of that brain, so we shouldn't bother. So for the second one, we have all the landmarks placed in their correct location and snapped to the mesh. Then we can visualize how far they were snapping from. There is a strange flop over here that we see, and I wonder where that got sourced from, but it seems like the landmarks snapped straight to it. One way to investigate things like this is we can go ahead and place our cursor on the area that we're interested in and then right click. That will position the crosshairs down here in the image automatically in the place where we clicked. So we can see that that flop actually does exist in the voxel image, which is this area that didn't get masked out. However, it is not part of the brain. For issues like this one, you will need to redo it, clean it up manually, or toggling on the option of applying the AI after the transfer. Options like this could enhance situations where unwanted sections get filtered through the process. Finally, we can just export this same landmarks here as a CSV. We can click that and you will see that it gets named different. Now it's brain landmarks export and now you can see the landmarks here are the ones for the brain and they are in the same exact space as the landmarks that we did for the outer surface of the head, which means that you could combine them to do analysis of covariance and things like this. Finally, before I finish this
Related: tutorial-ch45
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5356s · chapter 01:29:16
Tutorial transcript
- 5358.6s landmarks. Same way we did before, we go and click on snap landmarks to mesh, and then click on reference to subjects.
- 5368.48s Now that it's finished, we can see that new values appear in the landmarking information. So we won't even go check the one that's problematic. I'm just going to go check the other one, even though it says it's snapped correctly 100% from 83%. It won't be representative of the actual anatomy of
- 5387.0s that brain, so we shouldn't bother. So for the second one, we have all the landmarks placed in their correct location and snapped to the mesh. Then we can visualize how far they were snapping from. There is a strange flop over here that we see, and I wonder where that got sourced from, but it
- 5408.56s seems like the landmarks snapped straight to it. One way to investigate things like this is we can go ahead and place our cursor on the area that we're interested in and then right click.
- 5421.56s That will position the crosshairs down here in the image automatically in the place where we clicked.
- 5429.24s So we can see that that flop actually does exist in the voxel image, which is this area that didn't get masked out.
- 5439.0s However, it is not part of the brain. For issues like this one, you will need to redo it, clean it up manually, or toggling on the option of applying the AI after the transfer. Options like this could enhance situations where unwanted sections get filtered through the process. Finally, we can just export
- 5460.08s this same landmarks here as a CSV. We can click that and you will see that it gets named different. Now it's brain landmarks export and now you can see the landmarks here are the ones for the brain and they are in the same exact space as the landmarks that we did for the outer surface of the head, which
- 5480.28s means that you could combine them to do analysis of covariance and things like this. Finally, before I finish this
Additional tools
Denoise
Keywords: tutorial, transcript, video, youtube, denoise
Summary
Denoise with algorithm presets and live previews (optional auto-update); apply when satisfied, then clean mesh if filtering introduces outer-surface artifacts.
Algorithm / details
tutorial, I would like to show an option that I didn't dive deep into, which is the denoise. Now I quickly made this separate folder with a copy of one of the individuals. So what we're going to do here is we go up here into the options and we see denoise. Then we have a drop-down menu with a lot of denoising algorithms. The most common one that you might already have heard about is Gaussian filter. So one thing you can do with this is you can preview what this filter does. So if you toggle on denoise only current scan, it will show these two squares over here where we will see a preview. So, the way we do this is we put our crosshair somewhere where we want to see how the noise looks like. So, let's say we want to look at this area. Now, we go back here and then we click on generate preview. And now here we can see the original one and then the denoised version using the Gaussian filter and the parameters that we selected above here. Each algorithm will show you different parameters to set up. So, for example, the Gaussian filter here, it has a sigma and a truncate kernel radius. Now, if we want to see the preview automatically get updated the moment we change these parameters, we can toggle on the auto mode down here. But, bear in mind that for this to work smoothly, you need to have a very powerful computer that can actually recalculate the preview very fast. So, we can try different previews for different algorithms. The non-local means, for example, has a 3D GPU option and a 3D CPU option. Once you're happy with any of the results, you can click on the denoise button. And that's it, it just reloaded. Note that this denoising approach generated this outer surface bands that are unwanted. These ones you can get rid of just using the clean mesh, but now there is much less noise.
Related: tutorial-ch48
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5486s · chapter 01:31:26
Tutorial transcript
- 5489.28s tutorial, I would like to show an option that I didn't dive deep into, which is the denoise.
- 5496.28s Now I quickly made this separate folder with a copy of one of the individuals. So what we're going to do here is we go up here into the options and we see denoise.
- 5506.36s Then we have a drop-down menu with a lot of denoising algorithms. The most common one that you might already have heard about is Gaussian filter.
- 5516.28s So one thing you can do with this is you can preview what this filter does. So if you toggle on denoise only current scan, it will show these two squares over here where we will see a preview. So, the way we do this is we put our crosshair somewhere where we want to see how the
- 5534.76s noise looks like. So, let's say we want to look at this area. Now, we go back here and then we click on generate preview. And now here we can see the original one and then the denoised version using the Gaussian filter and the parameters that
- 5550.68s we selected above here. Each algorithm will show you different parameters to set up. So, for example, the Gaussian filter here, it has a sigma and a truncate kernel radius. Now, if we want to see the preview automatically get updated the moment we change these parameters, we can toggle on the auto
- 5569.28s mode down here. But, bear in mind that for this to work smoothly, you need to have a very powerful computer that can actually recalculate the preview very fast. So, we can try different previews for different algorithms. The non-local means, for example, has a 3D GPU option
- 5587.44s and a 3D CPU option. Once you're happy with any of the results, you can click on the denoise button. And that's it, it just reloaded. Note that this denoising approach generated this outer surface bands that are unwanted. These ones you can get rid of just using the clean
- 5607.08s mesh, but now there is much less noise.
Farewell message
Keywords: tutorial, transcript, video, youtube, farewell message
Summary
Closing notes: contact the lab via the website for questions, test datasets, or feature requests so Aurora can better fit your workflow.
Algorithm / details
Okay, that was it. Thank you so much for um following this tutorial, and I hope your questions about the program were clarified here. If you still have any remaining questions, you can email us through the website. If you have any specific data you would want to test, please let us know. We can help you out. Make sure it works flawlessly for your objectives. In the same way, if you have new features that you think would be very beneficial for you, let us know and we will add them to our future developments.
Related: tutorial-ch47
Video: https://www.youtube.com/watch?v=qWIXwDOJEUc&t=5611s · chapter 01:33:31
Tutorial transcript
- 5611.2s Okay, that was it. Thank you so much for um following this tutorial, and I hope your questions about the program were clarified here.
- 5621.68s If you still have any remaining questions, you can email us through the website. If you have any specific data you would want to test, please let us know. We can help you out. Make sure it works flawlessly for your objectives. In the same way, if you have new features that you think would be very beneficial
- 5640.28s for you, let us know and we will add them to our future developments.
Project & Scan I/O
File Browser & Upload
List Directories
Keywords: file browser, directory listing, filesystem, browse, extracted
Summary
Lists child directories and files at a filesystem path for Aurora's project file browser. Expands tilde to the user home directory, supports a special root alias, and returns absolute path plus parallel file size arrays. When browsing inside an extracted folder, it flattens one level into subject subfolders so the UI can show scan contents without extra navigation.
Considerations
Why use it
Powers the project file browser: it lists what is inside a folder so you can find and open a project without typing paths.
Use it when
- You browse to a project, a raw-scan folder or an export location from Aurora's file explorer. It runs automatically as you click through folders.
Skip it when
- You already know the exact path; pasting it is faster than browsing.
Why it works
It reads the folder's non-hidden entries and their sizes. Inside an extracted/ folder it shows each subject's files one level down, so you can see scan contents without opening every subject folder.
Signs of poor parameters
- A path that does not exist or that Aurora's engine cannot read (network drives that are not mounted, folders without permission). The browser then shows the folder as missing or empty.
How to check the result
- If a folder looks empty but is not, check that the drive is mounted on the computer running the engine (which may not be the computer you are browsing from).
Inputs / options
| Name | Type | Default | Description |
|---|
| path | string | ~ | URL-encoded directory path to list; use root for filesystem root. |
Outputs
| Name | Type | Description |
|---|
| directories | string[] | Child directory names (or flattened subject children when browsing extracted/). |
| files | string[] | Child file names listed alongside directories. |
| sizes | number[] | Byte sizes parallel to files (same order). |
| path | string | Absolute path that was listed. |
| missing | boolean | True when the requested path is not an existing directory. |
Algorithm / details
Expand and normalize the requested path, verify it is a directory, then enumerate non-hidden entries. Inside extracted/, list each subject folder's immediate children instead of top-level extracted entries. Return directories, files, sizes, absolute path, and missing flag when the path does not exist.
flowchart TD
A["GET path param"] --> B["Expand ~ / root"]
B --> C{"Path is directory?"}
C -->|"no"| D["Return missing=true"]
C -->|"yes"| E{"basename == extracted?"}
E -->|"yes"| F["List each subject subfolder contents"]
E -->|"no"| G["List immediate children"]
F --> H["Return dirs, files, sizes, path"]
G --> H
Related: quick-access-paths, create-folder
Quick Access Paths
Keywords: file browser, shortcuts, home, desktop, drives, OS paths
Summary
Returns OS-specific shortcut paths for the file browser: home, desktop, documents, downloads, and mount or drive roots. On Windows it enumerates drive letters; on Linux and macOS it adds common mount points such as /media, /mnt, /Volumes, and /run/media/<user> when present.
Considerations
Why use it
Gives the file browser one-click shortcuts (home, desktop, documents, downloads, drives and mounted disks) so you can jump to common places.
Use it when
- Your data is on an external disk or a mounted share: it appears among the shortcuts once the operating system has mounted it.
Why it works
It detects the operating system and lists the standard user folders plus drive letters (Windows) or the usual mount locations such as /media, /mnt and /Volumes (Linux and macOS).
How to check the result
- If a disk you just plugged in is missing, reopen the browser after the operating system has finished mounting it.
Algorithm / details
Detect platform with platform.system(), build standard user folders from Path.home(), then append drive letters on Windows or known mount directories on Linux and macOS. Fail gracefully and still return home paths if enumeration errors occur.
flowchart TD
A["Detect OS"] --> B["Add home/desktop/documents/downloads"]
B --> C{"OS type?"}
C -->|"Windows"| D["psutil.disk_partitions drive letters"]
C -->|"Darwin"| E["/Volumes, /mnt if exist"]
C -->|"Linux"| F["/media, /mnt, /run/media/user"]
D --> G["Return quick_paths JSON"]
E --> G
F --> G
Related: list-directories
Create Folder
Keywords: create, folder
Summary
Creates a new subdirectory under a path chosen in Aurora's file explorer. Use when organizing project folders (for example before importing scans) without leaving the UI.
Considerations
Why use it
Creates a new folder from inside Aurora, for example a fresh project folder before you copy or upload raw scans into it.
Use it when
- You want to keep each study in its own project folder without leaving Aurora.
Signs of poor parameters
- A name that already exists in that location, or characters your operating system does not allow in folder names. The request fails and nothing is created.
Good parameters
- Short names without spaces or special characters; they are easier to handle in scripts and exports later.
Inputs / options
| Name | Type | Default | Description |
|---|
| path | string | required | Parent directory in which to create the folder. |
| folderName | string | required | Name of the new folder to create under path. |
Outputs
| Name | Type | Description |
|---|
| message | string | Success text when the folder is created. |
| error | string | Error detail when creation fails (HTTP 400). |
Algorithm / details
1) Read path and folderName from the POST body. 2) Join them into a full filesystem path. 3) Call os.makedirs to create the directory (fails if it already exists or the parent is invalid). 4) Return a success message, or an error string with status 400 on exception.
flowchart TD
A["POST path, folderName"] --> B["Join full path"]
B --> C["os.makedirs"]
C -->|"ok"| D["message: Folder created"]
C -->|"error"| E["error + HTTP 400"]
Related: list-directories, file-upload, file-copy
File Upload
Keywords: file, upload
Summary
Uploads a multipart file into a chosen project folder so it becomes available on disk for extraction or import. Use when bringing external files into the open Aurora project from the file browser.
Considerations
Why use it
Copies a file from your browser into a project folder on the computer running Aurora's engine, which matters when you work remotely and the data lives on another machine.
Use it when
- You use Aurora in the browser against an engine on another computer and need to bring a raw scan or mesh file to it.
Skip it when
- The engine runs on your own computer and the file is already on its disks: point the project at it instead of uploading a copy.
Why it works
The file is streamed in chunks and written under its original name in the target folder.
Signs of poor parameters
- A target folder that already holds a file with the same name: it is overwritten.
How to check the result
- Browse to the target folder afterwards and check that the file size matches the original.
Inputs / options
| Name | Type | Default | Description |
|---|
| target_folder | string | . | Directory where the uploaded file is written; defaults to the current directory. |
| file | file | required | Multipart file field (request.FILES['file']) whose original name is used on disk. |
Outputs
| Name | Type | Description |
|---|
| message | string | Confirms the file was written successfully. |
Algorithm / details
1) Read target_folder from the POST body (default '.'). 2) Take the uploaded multipart file from request.FILES['file']. 3) Write it chunk-by-chunk to target_folder/file.name. 4) Return a success message. Uses MultiPartParser and FormParser.
flowchart TD
A["POST multipart file"] --> B["Resolve target_folder"]
B --> C["Write chunks to disk"]
C --> D["message: uploaded"]
Related: create-folder, file-copy, list-directories
File Copy
Keywords: file, copy
Summary
Copies one or more source folder trees into a target folder inside the managed project filesystem. Use when duplicating subject or dataset directories without re-uploading.
Considerations
Why use it
Duplicates whole folders (for example subject or dataset folders) inside the engine's filesystem without re-uploading them.
Use it when
- You want to try a processing branch on a copy of a project, or assemble a new cohort from folders that already exist on the engine computer.
Signs of poor parameters
- Copying very large projects onto a nearly full disk. Check free space first (the project size is shown in the working directory panel).
How to check the result
- Open the copied folder and check that the subject count matches the source.
Inputs / options
| Name | Type | Default | Description |
|---|
| source_paths | string[] | required | Source path(s) resolved via find_absolute_paths / extract_folder_paths before copy. |
| target_folder | string | required | Destination parent directory for each copied tree. |
Outputs
| Name | Type | Description |
|---|
| message | string | Success text when all directories copy. |
| error | string | Validation or copy failure detail (400/500). |
Algorithm / details
1) Resolve source_paths with find_absolute_paths and extract_folder_paths; read target_folder. 2) Reject the request if sources or target are missing, or if resolved sources are not a list. 3) For each source path, shutil.copytree into target_folder/<basename>. 4) Return success, or an error with HTTP 500 if copy fails.
flowchart TD
A["POST source_paths, target_folder"] --> B["Resolve absolute folder paths"]
B --> C{"Valid list + target?"}
C -->|"no"| D["HTTP 400 error"]
C -->|"yes"| E["copytree each source"]
E -->|"ok"| F["Directories copied"]
E -->|"fail"| G["HTTP 500 error"]
Related: create-folder, file-upload, list-directories
get Data Size Directory
Keywords: get, data, size, directory
Summary
Reports filesystem capacity and the approximate on-disk size of a project directory in gigabytes. Use before large imports or batch jobs to see how much space the project already occupies; large trees may be deferred or skipped so the UI stays responsive.
Considerations
Why use it
Shows how much space a project uses and how much is left on the disk, so you can see in advance whether a large batch step will fit.
Use it when
- Before extraction, elastic registration or other steps that write a new full-resolution edit for every subject.
Why it works
It reads the disk's total and used space, then adds up the project's file sizes for at most a couple of seconds. Large projects may show a partial or deferred size the first time; the result is cached for 30 seconds.
How to check the result
- A size that keeps showing as unavailable on a very large project is expected; reopen the panel a moment later to get the cached value.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project or folder path to measure; expanded with ~ and validated as an existing directory. |
Outputs
| Name | Type | Description |
|---|
| total_size | number | Total capacity of the filesystem containing the directory, in GB. |
| occupied_size | number | Used capacity of that filesystem, in GB. |
| size | number|null | Sum of file sizes under the directory in GB, or null if the timed walk was skipped. |
| complete | boolean | True when the directory walk finished within the time budget. |
| cached | boolean | True when the payload was served from the short-lived in-memory cache. |
| deferred | boolean | True when another scan for this directory is already in flight (may return HTTP 202). |
| message | string | Human-readable note when the scan is deferred, timed out, or otherwise incomplete. |
| error | string | Validation or OS error detail (HTTP 400). |
Algorithm / details
1) Normalize directory to an absolute existing path. 2) If a cache entry younger than CACHE_TTL_SECONDS (30) exists, return it with cached=true. 3) If a scan is already in flight for this directory, return the previous cached result (deferred) or disk usage only with size=null and HTTP 202. 4) Otherwise mark the directory in-flight, read total/used disk via psutil.disk_usage, and walk files summing sizes until MAX_SCAN_SECONDS (2.5) elapses. 5) Set size and complete; cache a full result when the walk finishes; always clear the in-flight mark.
flowchart TD
A["POST directory"] --> B["Normalize path"]
B --> C{"Fresh cache?"}
C -->|"yes"| D["Return cached payload"]
C -->|"no"| E{"Scan in flight?"}
E -->|"yes"| F["Return deferred / 202"]
E -->|"no"| G["Walk files up to 2.5s"]
G --> H["Return disk + size"]
Related: list-directories, clear-intermediate-files
Upload File
Keywords: multipart upload, file transfer, import, target folder
Summary
Uploads a single file via multipart form data into a target folder on the Aurora server filesystem. Streams chunks to disk to support large scan archives or mesh files imported through the UI.
Considerations
Why use it
Uploads a single large file (a scan archive or a mesh) into a folder on the engine computer, streaming it to disk so big files do not need to fit in memory.
Use it when
- You work in the browser against a remote engine and need a file from your own computer there.
Skip it when
- The file is already on the engine computer.
Signs of poor parameters
- A target folder that already holds a file with the same name: it is overwritten.
Inputs / options
| Name | Type | Default | Description |
|---|
| file | file (multipart) | | Binary file payload in request.FILES. |
| target_folder | string | . | Destination directory on the server where the uploaded file is saved. |
Algorithm / details
Read the uploaded file from multipart request.FILES, join target_folder with the original filename, and write chunked bytes to disk.
flowchart TD
A[Multipart POST] --> B[Read file chunks]
B --> C[Write to target_folder/file.name]
C --> D[Return success]
Related: copy-folder, list-directories
Copy Folder
Keywords: copytree, duplicate project, folder copy, import
Summary
Recursively copies one or more source directories into a target folder. Resolves relative source paths under the user home directory when needed, then copies each folder tree preserving the basename.
Considerations
Why use it
Copies one or more folders into a target folder, keeping each folder's name.
Use it when
- You assemble a project from folders that already exist on the engine computer.
Signs of poor parameters
- Relative names that match more than one folder under your home directory. The copy is refused rather than guessing; use full paths.
How to check the result
- Open the target folder and check that every source folder arrived with its contents.
Inputs / options
| Name | Type | Default | Description |
|---|
| source_paths | string[] | | Relative or absolute folder paths to copy; resolved via home-directory glob search. |
| target_folder | string | | Destination parent directory receiving copied folder trees. |
Algorithm / details
Resolve source_paths to absolute unique parent folders, then shutil.copytree each source into target_folder using its basename. Reject ambiguous or missing matches from path resolution.
flowchart TD
A[source_paths + target_folder] --> B[Resolve absolute paths]
B --> C{Valid list?}
C -->|no| D[400 error]
C -->|yes| E[copytree each source]
E --> F[Return success]
Related: upload, list-directories
Project Files & Settings
Flag Subjects
Keywords: flag, subjects
Summary
Sets or clears the flag field on one or more subject metadata JSON files under extracted/. Use when marking subjects so batch tools and the UI can include or exclude them consistently.
Considerations
Why use it
Marks subjects with a flag so batch tools can run on only those subjects, or skip them. It is the way to process a chosen subset without moving files around.
Use it when
- You want to redo one step for a handful of subjects (only flagged), or hold some subjects back from a batch (exclude flagged).
- You want to keep a visible to-do or review marker on subjects.
Skip it when
- The subject is broken and should never be processed; mark it faulty instead (Report Bad File).
Effect on later steps
- Batch tools with a flag filter (Helps): Denoise, Intensity Normalization, Align to reference, Elastic registration, Transfer landmarks and others can run on flagged subjects only, or exclude them.
Why it works
It sets or clears a flag field in each subject's metadata file. It does not change any image data.
How to check the result
- After flagging, check the subject cards show the flag before starting the batch, and that the batch's flag filter is set the way you intend.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root containing extracted/<subject>/<subject>.json. |
| filenames | string|string[] | required | Subject folder name(s) whose metadata JSON should be updated; a single string is wrapped into a list. |
| flag | boolean | True | Value written to metadata flag; coerced with bool(). |
Outputs
| Name | Type | Description |
|---|
| message | string | Success text when at least one subject was updated. |
| updated | string[] | Subject names whose metadata was written successfully. |
| errors | object[] | Per-subject failures (invalid name, missing JSON, I/O/JSON errors). |
| confirmed_flags | object | Map of subject name to the flag value re-read from disk after write. |
| error | string | Top-level error when no subjects could be updated. |
| details | object[] | Error details when the whole request fails with no updates. |
Algorithm / details
1) Require directory and a non-empty filenames list (normalize a scalar to a list). 2) For each subject name, open extracted/<name>/<name>.json, set flag to the boolean value, write indented JSON, and re-read to confirm. 3) Collect updated names, confirmed_flags, and per-file errors. 4) If nothing updated and errors exist, return HTTP 400; otherwise return the update summary.
flowchart TD
A["POST directory, filenames, flag"] --> B["Normalize filenames list"]
B --> C["For each subject JSON"]
C --> D["Write flag + confirm"]
D --> E{"Any updated?"}
E -->|"no"| F["HTTP 400 + details"]
E -->|"yes"| G["updated, confirmed_flags"]
Related: report-bad-file, list-directories
Report Bad File
Keywords: report, bad, file
Summary
Marks a subject as faulty (or clears that mark) in its extracted metadata JSON. Use when a scan should be skipped or highlighted as broken in downstream batch processing.
Considerations
Why use it
Marks a subject as faulty (or clears the mark). Faulty subjects are skipped by every batch tool and by exports, so one broken scan does not stop or contaminate the cohort.
Use it when
- A scan is corrupted, incomplete, the wrong specimen, or too poor to process.
- A subject keeps failing a batch step and you want the rest of the cohort to continue.
Skip it when
- You only want to leave a subject out of one step; use the flag filter instead, so it stays in later steps and exports.
Effect on later steps
- All batch tools and exports (Caution): Intended, just be aware: faulty subjects are left out of processing, atlases and landmark exports until the mark is cleared.
- Clear Intermediate Files (Caution): Real risk (cannot be undone): deletes faulty subjects from extracted/ and moves their raw files to faulty/.
Why it works
It writes faulty = true or false into the subject's metadata file. No image data changes.
How to check the result
- Before exporting, review the list of faulty subjects so that nobody was left out by mistake.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root containing extracted/<filename>/<filename>.json. |
| filename | string | required | Subject folder name whose metadata JSON is updated. |
| faulty | boolean | required | Faulty status to store; coerced with bool(), or False when null. |
Outputs
| Name | Type | Description |
|---|
| message | string | Confirms the faulty status was written. |
Algorithm / details
1) Build the path extracted/<filename>/<filename>.json under directory. 2) Load the JSON, set faulty to bool(faulty) or False when faulty is null. 3) Write the metadata back with indent=4. 4) Return a success message.
Related: flag-subjects, list-directories
Clear Intermediate Files
Keywords: cleanup, storage, elastic lock-in, faulty delete, intermediate edits
Summary
Irreversibly cleans completed projects to save disk space. Deletes faulty subjects from extracted/ and moves their raw counterparts into directory/faulty/. For subjects with completed elastic registration, keeps only the elastic edit and the immediately prior edit plus originals, deletes other intermediate edits, and trims lossy_compression metadata to the last two entries. Emits WebSocket progress per subject.
Considerations
Why use it
Frees disk space on a finished project by deleting the intermediate edits you no longer need. It cannot be undone.
Use it when
- The project is finished (elastic registration done, landmarks exported) and disk space is tight.
Skip it when
- You may still need to go back to an intermediate step, change parameters, or rerun something.
- You have not backed up the project. Deleted edits cannot be recovered; this is a real, irreversible risk.
Effect on later steps
- Any later reprocessing (Caution): Real risk (cannot be undone): only the elastic edit, the edit just before it and the originals remain, so redoing an earlier step means starting from the original scan.
Why it works
Faulty subjects are removed from extracted/ and their raw files are moved to faulty/. For subjects that finished elastic registration it keeps the original, the elastic edit and the edit just before it, and deletes the other edits and their side files.
Signs of poor parameters
- Running it while a subject is still mid-pipeline or before its elastic registration: those subjects are not trimmed, but faulty ones are still removed.
How to check the result
- Check the project size before and after, and open a few subjects to confirm the elastic edit and its landmarks still load.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted/ subjects. |
Algorithm / details
Iterate extracted subjects, delete entire folder when faulty (track raw names for faulty/ relocation), else find latest elastic edit number, retain elastic and edit N-1 files plus originals, delete other edits and auxiliary files, prune lossy_compression list, update JSON, report space freed.
K = \{E_{\mathrm{elastic}},\, E_{\mathrm{elastic}}-1\} \cup \mathrm{originals}
K: files kept after cleanup; E_elastic: latest elastic-registration edit index; originals: unedited source files.
flowchart TD
A["directory"] --> B["For each extracted subject"]
B --> C{"faulty?"}
C -->|"yes"| D["Delete folder + queue raw move"]
C -->|"no"| E{"Has elastic edit?"}
E -->|"no"| F["Skip"]
E -->|"yes"| G["Delete intermediate edits"]
G --> H["Trim lossy_compression metadata"]
D --> I["Progress + summary"]
F --> I
H --> I
Related: report-bad-file, create-atlas
Get Project Settings
Keywords: get, project, settings
Summary
Loads project-wide settings from extracted/project_settings.json for the open directory. Use when the UI needs the selected reference subject and general preferences after opening a project.
Considerations
Why use it
Loads the project-wide choices (the reference subject, general settings and landmark groups) when you open a project, so you continue where you left off.
Use it when
- It runs automatically when a project opens.
Why it works
It reads extracted/project_settings.json. If the file is missing or unreadable, Aurora starts with no reference and default settings.
How to check the result
- If the reference or settings look reset after opening a project, check that extracted/project_settings.json exists and is valid JSON.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root; settings are read from extracted/project_settings.json. |
Outputs
| Name | Type | Description |
|---|
| selected_reference | string | Saved reference subject name, or empty string if unset/missing/corrupt. |
| general_settings | object | Saved general settings object, or {} if unset/missing/corrupt. |
| landmark_groups | array | Named landmark groups (colors / membership) stored for the project, or [] if unset/missing/corrupt. |
Algorithm / details
1) Resolve extracted/project_settings.json under directory. 2) If the file exists, load selected_reference, general_settings, and landmark_groups (defaults '', {}, and []). Coerce a non-list landmark_groups value to []. 3) If missing or unreadable, return empty selected_reference, empty general_settings, and []. 4) Never invent other keys beyond what is stored in that JSON.
Related: save-project-settings, get-label-names
Save Project Settings
Keywords: save, project, settings
Summary
Persists the selected reference and general settings into extracted/project_settings.json. Use when the user changes project-wide UI or processing preferences that should survive reloads.
Considerations
Why use it
Saves project-wide choices (the reference subject, general settings and landmark groups) so they survive reloads and are shared by everyone who opens the project.
Use it when
- It runs automatically when you change the reference or a project setting.
Effect on later steps
- Every reference-based tool (Caution): Real risk of mixing references in one cohort: changing the reference here changes the target of Align to reference, Elastic registration, Align Thresholds and the transfers; subjects already processed against the old reference are not redone automatically.
Why it works
It writes the values into extracted/project_settings.json, keeping landmark groups unchanged unless new ones are sent.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root; creates extracted/ if needed. |
| selected_reference | string | | Reference subject name stored in project settings. |
| general_settings | object | {} | Arbitrary general settings object written alongside the reference. |
| landmark_groups | array|null | | When provided (including []), replace stored landmark groups. Omitted leaves existing landmark_groups unchanged. |
Outputs
| Name | Type | Description |
|---|
| message | string | Success text when settings are written. |
| error | string | Failure detail (HTTP 400). |
Algorithm / details
1) Read directory, selected_reference (default ''), general_settings (default {}), and optional landmark_groups. 2) Ensure the extracted/ parent exists. 3) Load existing project_settings.json or start with {}. 4) Overwrite selected_reference and general_settings; if landmark_groups was sent, replace that list (coerce a non-list to []). 5) Write indent=4 JSON and return success or an error.
flowchart TD
A["POST settings fields"] --> B["makedirs extracted/"]
B --> C["Merge into project_settings.json"]
C --> D["Write JSON"]
D --> E["message or error"]
Related: get-project-settings, save-label-names
Delete Edit
Keywords: delete, edit
Summary
Deletes a numbered edit slot (or the entire raw subject when edit is null) for a scan, including companion masks, projections, landmarks, and registration sidecars, then cleans related metadata. Use when rolling back a processing step or removing a bad edit from the timeline.
Considerations
Why use it
Removes one step from a subject's edit history (with its masks, landmarks and registration files), so you can undo a step and rerun it with other parameters.
Use it when
- A step gave a poor result for a subject and you want to redo it.
- A batch tool skips a subject because that step was already applied, and you want to rerun it.
Skip it when
- You only want to try something different temporarily; later edits build on the deleted one, so delete from the latest edit backwards.
Effect on later steps
- Later edits (Caution): Real risk to the edit history: edits created after the deleted one were computed from it; delete from the newest edit backwards to keep the history consistent.
Why it works
It deletes the edit's image or mesh files and every companion file, then undoes the metadata that step had written (for example the threshold shift from Background Offset Correction, or the alignment record). With no edit given, it deletes the subject's base files.
How to check the result
- After deleting, check the subject's timeline shows the previous edit as latest and that its threshold and mesh look like before the step.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root for the subject (or atlas) being edited. |
| filename | string | required | Subject name (or atlas) whose edit files live under extracted/<filename>/ (atlas uses the atlas folder). |
| edit | string|null | required | Edit slot index/string to delete; when null/absent path, deletes raw base volumes/mesh and metadata instead. |
Outputs
| Name | Type | Description |
|---|
| message | string | Edit deleted on success, or Edit not found when no matching edit files existed. |
| previous_edit | string|null | Basename of the prior edit file when edit > 0 and a previous slot exists; otherwise null. |
Algorithm / details
1) Resolve scan_dir (atlas folder for filename atlas, else extracted/<filename>) and load metadata to detect preserved mesh (is_mesh true and voxelized false), falling back to PLY globs. 2) If edit is set: glob edit NIfTI/lossy/PLY payloads, projection PNGs, landmarks, elastic .mat, guidepoints, masks (.nii.mask.gz), and mesh .npy/.npz caches; delete them. 3) Update subject JSON: trim lossy_compression entries; clear alignment / elastic / backfixed / landmarks / histogram-match metadata (and restore threshold shifts) based on filename tags; for masked edits, optionally restore elastic backups and un-bump elastic files from edit+1 back to this slot. 4) If edit is null: remove base .nii.gz / lossy / .ply, base projections, JSON, and landmark files (and wipe atlas folder contents when applicable). 5) Return previous_edit (prior slot basename when edit > 0) with message Edit deleted, or 404 when no edit files matched.
flowchart TD
A["POST directory, filename, edit"] --> B{"edit set?"}
B -->|"no"| C["Delete raw + metadata"]
B -->|"yes"| D["Glob edit payloads + sidecars"]
D --> E{"Files found?"}
E -->|"no"| F["404 Edit not found"]
E -->|"yes"| G["Delete files"]
G --> H["Clean JSON metadata"]
H --> I["Return previous_edit"]
C --> I
Related: projection-png, overwrite-threshold, clear-intermediate-files
Extract & Display
Display NIfTI
Keywords: NIfTI download, lossy volume, full resolution, slice viewer, binary stream
Summary
Serves a scan or edit NIfTI file as a binary download for the frontend slice viewer. By default returns the requested lossy or full-res file on disk; when request_full_res is true, loads the lossless volume, reapplies stored lossy compression parameters, and streams a freshly compressed preview without downsampling resolution.
Considerations
Why use it
Sends a scan or edit to the slice viewer. By default it sends the light preview copy so browsing stays fast; on request it rebuilds the preview from the full-resolution data.
Use it when
- It runs automatically when you view slices. Full resolution is used when you ask for detail beyond the preview.
Why it works
The preview copy is a compressed version of the scan. Full-resolution requests re-create that compression from the original file without lowering the resolution.
How to check the result
- If a thin structure looks broken in the viewer, check it at full resolution before concluding the data is damaged; the preview may hide it.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Base or lossy NIfTI filename for the subject. |
| edit | string|null | | Specific edit filename; when set and containing edit, replaces filename. |
| request_full_res | boolean | False | When true, rebuild compressed preview from lossless NIfTI using metadata lossy_compression settings. |
Algorithm / details
Resolve subject path under extracted/ or atlas/, optionally swap to edit filename, then either stream the on-disk file or load lossless NIfTI, read scale_factor/zero_point_shift/resolution_factor from metadata, recompress in memory, and return FileResponse octet-stream attachment.
v_{\mathrm{lossy}} = (v_{\mathrm{raw}} - z) / s
v_lossy: streamed 8-bit intensity; v_raw: full-resolution value; z: zero_point_shift; s: scale_factor from metadata.
flowchart TD
A["directory + filename + edit"] --> B{"request_full_res?"}
B -->|"no"| C["Stream existing NIfTI file"]
B -->|"yes"| D["Load lossless + metadata params"]
D --> E["Recompress without shrink"]
E --> F["Stream compressed bytes"]
Related: nifti-header-info, marchingcubes
Projection Png
Keywords: projection, png
Summary
Serves a cached isometric projection PNG for a scan edit (front or back) used by the grid and subject cards. Use when the UI needs a lightweight preview without loading the full volume or mesh.
Considerations
Why use it
Serves the small pre-rendered 3D preview images used on subject cards and in the grid, so you can scan a cohort quickly without loading every volume.
Use it when
- It runs automatically when cards and the grid are shown.
How to check the result
- A preview that looks outdated after an edit is regenerated by Ensure Quick Grid Projections or by opening the subject.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Query param: project root (URL-decoded, ~ expanded). |
| scan_name | string | required | Query param: subject name (or atlas); must stay under the project directory. |
| edit | string | required | Query param: edit volume/mesh filename used to derive projection candidate paths. |
| side | string | optional | Query param: when back, prefer *_projection_back.png; otherwise front *_projection.png. |
Outputs
| Name | Type | Description |
|---|
| (binary PNG) | image/png | FileResponse body of the first matching projection PNG on disk. |
| error | string | JSON error when params are missing, the path is invalid, or no PNG is found. |
Algorithm / details
1) Require directory, scan_name, and edit query params; expand/unquote and reject paths outside the project. 2) Resolve scan_dir (atlas folder or extracted/<scan_name>). 3) Build candidate PNG paths from the edit name (PLY stem, lossy edit variants, or lossy projection suffixes); pick the first existing .png. 4) If none match, call resolve_mesh_projection_path. 5) Stream the PNG as image/png, or return 404 when missing.
flowchart TD
A["GET directory, scan_name, edit, side"] --> B["Validate under project"]
B --> C["Try projection candidates"]
C --> D{"PNG found?"}
D -->|"no"| E["resolve_mesh_projection_path"]
D -->|"yes"| F["FileResponse PNG"]
E --> F
E -->|"still none"| G["HTTP 404"]
Related: ensure-mesh-projection-pngs, ensure-quick-grid-projections, delete-edit
Physically Accurate Mesh Export
Keywords: PLY export, GLB export, physical coordinates, marching cubes, download mesh
Summary
Exports a subject mesh in physical millimeter coordinates for external tools. Voxel scans run full-resolution marching cubes at the stored threshold (optional Gaussian pre-blur), apply Aurora's X/Z axis convention, and write both GLB and ASCII PLY to the subject folder before streaming the PLY download. Preserved mesh subjects stream the native PLY edit directly without re-voxelization.
Considerations
Why use it
Exports a subject's surface in real millimetres, in the same axis convention Aurora uses, for analysis or printing in other software.
Use it when
- You need the surface outside Aurora: other morphometrics software, 3D printing, or figures.
Skip it when
- You only need landmark coordinates; Export landmarks is lighter and keeps the same coordinates.
Why it works
Voxel scans are meshed at full resolution at the stored threshold (with an optional blur first) and saved as GLB and PLY; preserved meshes are exported as they are.
Signs of poor parameters
- A large blur. It rounds off thin and sharp structures, so the exported surface is no longer faithful to the scan.
Good parameters
- No blur, or a small one (around 0.5–1 voxel) only to remove voxel staircase noise.
How to check the result
- Open the PLY in another tool and measure a known distance to confirm the scale is in millimetres.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject folder name or atlas. |
| edit | string|null | | Edit filename; lossy suffix stripped for full-res source. |
| gaussian_blur | number | 0.0 | Gaussian sigma applied before marching cubes; clamped to 0–10. |
Algorithm / details
If preserved mesh metadata, resolve PLY edit path and stream binary PLY. Else load full-res NIfTI, optional gaussian_filter, marching_cubes at metadata threshold with physical spacing, swap X/Z axes and fix winding, save GLB and ASCII PLY beside subject, return PLY FileResponse attachment.
V = \mathrm{MC}(I,\, \tau,\, \Delta)
V: mesh vertices; MC: marching cubes; I: volume; τ: iso-threshold from metadata; Δ: physical voxel spacing.
flowchart TD
A["directory + filename + edit"] --> B{"Preserved mesh?"}
B -->|"yes"| C["Stream PLY edit"]
B -->|"no"| D["Load full-res volume"]
D --> E["Optional Gaussian blur"]
E --> F["Marching cubes at threshold"]
F --> G["Save GLB + PLY, stream PLY"]
Related: marchingcubes, extract-scans
Get All Subjects Mesh Overlay
Keywords: overlay, crop mode, combined mesh, OR mask, preserved PLY, reference frame
Summary
Builds one combined GLB overlay of all non-faulty subjects aligned in the reference display frame for crop-mode show-all-scans. Voxel subjects contribute threshold masks resampled to a shared reference grid, unioned, largest-component filtered, and meshed with marching cubes; preserved PLY subjects load decimated native meshes in shared millimeters. Merges all parts, centers/scales like the reference Quick Mesh, and returns GLB plus overlay debug stats.
Considerations
Why use it
Builds one combined surface of every subject in the reference frame, so when cropping you can see how much space all subjects together occupy and avoid cutting any of them.
Use it when
- You crop the reference (or define a region) and want the box to cover every subject after alignment.
Skip it when
- Subjects are not aligned yet; the overlay then shows them in different places and is not meaningful.
Why it works
Voxel subjects are thresholded in the reference grid, combined, reduced to the largest connected piece and meshed; mesh subjects are added as lighter copies of their surfaces.
How to check the result
- The overlay should look like a slightly blurred version of one specimen. Parts sticking out far usually mean one subject is misaligned.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| reference | string | | Reference subject name defining the shared overlay coordinate frame. |
| full_resolution | boolean | False | When true, use full-resolution reference voxel context for mask resampling. |
Algorithm / details
List valid non-faulty extracted subjects, resolve reference anchor and voxel grid, for each voxel subject load mask in reference space and keep largest connected component, OR-combine masks and marching-cubes the union, for each PLY subject load shared-mm mesh and decimate to per-subject budget, merge meshes, apply reference center/scale, build GLB via MarchingCubesView helper.
M_{\cup} = \bigvee_i M_i
M_∪: combined foreground mask; M_i: largest connected component of subject i in reference space; ∨: voxel-wise OR.
S = \mathrm{MC}(M_{\cup})
S: overlay surface; MC: marching cubes on the union mask.
flowchart TD
A["reference + directory"] --> B["Split voxel vs PLY subjects"]
B --> C["Voxel: resample masks → OR → MC"]
B --> D["PLY: load shared-mm meshes"]
C --> E["Merge + reference center/scale"]
D --> E
E --> F["Return GLB + overlay_debug"]
Related: marchingcubes, apply-crop
Volume Edits
Apply Rotation
Keywords: rotate, 3D rotation, affine, batch rotate, reorientation
Summary
Applies a 3D rotation from the Quick Mesh Canvas to a subject NIfTI edit or to all scans in a project. Converts the BabylonJS rotation matrix to scipy affine_transform coordinates, rotates around the mesh center stored in metadata, uses tight bounding-box sizing to limit memory, saves a new *_edit_* rotated volume plus lossy copy, and propagates masks and landmarks.
Considerations
Why use it
Rotates a scan (or all scans) into a chosen orientation, for example a standard anatomical pose, so that later steps and views start from a consistent position.
Use it when
- Specimens were scanned in different poses and you want a consistent starting orientation before alignment.
- You want views and crops to line up with anatomical axes.
Skip it when
- Align to reference will run next anyway and you do not need a particular pose; rigid alignment handles orientation differences itself (except for extreme cases).
Effect on later steps
- Align to reference (Helps): a rough consistent orientation makes rigid alignment faster and less likely to settle on a wrong pose.
- Masks and landmarks (No difference): they are rotated with the scan.
Why it works
The rotation you set in the 3D view is applied to the volume around the specimen's centre. The new volume is sized to fit the rotated specimen with a 40-voxel background border, which reduces edge artefacts in later registration.
Signs of poor parameters
- Nearest interpolation for intensity images; it creates jagged edges. Use it only for label volumes.
Good parameters
- Linear interpolation for scans (the default).
How to check the result
- Check the mesh and a few slices after rotating: the specimen should be whole, not clipped at the edges, and masks and landmarks should still sit on it.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject name for single-scan rotation; reference context when all is true. |
| edit | string|null | | Source edit filename; null uses base <filename>.nii.gz. |
| rotation | object | | BabylonJS rotation payload with _m indexed matrix entries remapped to 3×3 numpy rotation. |
| geometry | object|null | null | Optional linked-group replay payload from a prior single-scan rotation: center_voxel, box_min/box_max, final_bbox_min/final_bbox_max, source_shape. When present, siblings reuse the authored scan’s centre/box/crop instead of recomputing. |
| interpolation | string | linear | nearest (order 0) or linear (order 1) interpolation during affine resampling. |
| all | boolean | False | When true, apply the same rotation to all eligible scans via rotate_all_scans. |
Algorithm / details
Build rotation_matrix from rotation._m, load full-res NIfTI and metadata center converted to voxel ZYX, estimate background, compute the minimal rotated axis-aligned bounding box, affine_transform the volume about center (linear or nearest-neighbor for labels), keep a border of 40 background voxels on all faces to reduce later registration edge artifacts, save new edit and lossy companion, copy/transform paired masks and landmarks.
v' = R(v - c) + c + o
v: input voxel; R: rotation; c: rotation center (voxel ZYX); o: optional offset. Output canvas includes a 40-voxel background border on each face.
flowchart TD
A["rotation matrix + edit"] --> B{"all scans?"}
B -->|"yes"| C["rotate_all_scans"]
B -->|"no"| D["Load volume + mesh center"]
D --> E["Compute tight rotated bbox"]
E --> F["affine_transform"]
F --> G["Save edit + lossy + propagate masks/LM"]
Related: apply-crop, marchingcubes
Apply Crop
Keywords: crop, bounding box, padding, edit, mask propagation, landmarks
Summary
Crops a subject volume to a 3D bounding box from the UI, adds configurable padding filled with the estimated background value, and saves a new *_edit_*_cropped NIfTI plus matching lossy edit. Propagates paired masks and landmarks with identical crop/pad geometry, filtering landmarks outside the crop.
Considerations
Why use it
Cuts the volume down to the region you need. Smaller volumes process much faster, and removing unrelated structures (holders, other specimens, empty space) makes registration and segmentation more reliable.
Use it when
- The scan contains a lot of empty space, a specimen holder, or neighbouring specimens.
- You only study one region of the specimen.
Skip it when
- The specimen already fills the volume tightly.
Effect on later steps
- Background Offset Correction (Caution): Low risk with the default padding: keep some air around the specimen; the background estimate needs background voxels. The default padding provides that.
- Intensity Normalization (Caution): Moderate risk, mostly for the upper percentile once Background Offset Correction anchors the low end: percentiles count every voxel, so crop subjects in a similar way to keep the background fraction comparable.
- Registration (Helps): less unrelated structure to mismatch, and much faster runs.
Why it works
The volume is cut to the box (plus a 2-voxel margin) and then padded on every side with the estimated background value. Masks get the same crop; landmarks outside the box are dropped.
Signs of poor parameters
- A box that cuts into the anatomy you study. Lost data cannot be recovered without deleting the edit.
- Zero padding. The specimen then touches the volume edge, which hurts background estimation, meshing and registration.
Good parameters
- A box with a small margin around the anatomy of interest and the default padding (40 voxels).
How to check the result
- Use the all-subjects overlay on the reference to confirm the box contains every subject, and check that no landmarks were dropped unexpectedly.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject folder name. |
| edit | string|null | | Source edit filename; null uses base full-res NIfTI. |
| crop | number[6] | | Axis-aligned bounds [x_min, y_min, z_min, x_max, y_max, z_max] in voxel order mapped to ZYX slices with ±2 voxel margin. |
| padding | number | 40 | Voxels of background padding added on every face after cropping. |
Algorithm / details
Load full-res NIfTI (strip _lossy from edit), map crop box to ZYX indices with small margin, slice volume, pad with background_value, save cropped edit and lossy derivative, mirror crop/pad on .nii.mask.gz if present with downsampled lossy mask, transform landmarks and filter distance arrays to kept indices.
V_{\mathrm{crop}} = \mathrm{pad}\!\left(V[z_0{:}z_1,\, y_0{:}y_1,\, x_0{:}x_1],\, p,\, b\right)
V: volume; [z0:z1, …]: crop box in ZYX; p: padding width; b: background fill value.
flowchart TD
A["crop box + edit"] --> B["Load full-res volume"]
B --> C["Slice ZYX with margin"]
C --> D["Pad with background"]
D --> E["Save cropped edit + lossy"]
E --> F["Propagate mask + landmarks"]
Related: apply-rotation, get-all-subjects-mesh
Overwrite Threshold
Keywords: overwrite, threshold
Summary
Writes a new integer threshold into a subject's (or atlas) metadata JSON for the edit being viewed. Latest non-elastic edits update the global threshold; older or elastic edits upsert into old_thresholds.
Considerations
Why use it
Saves the threshold you chose by eye for a subject. The threshold defines where the specimen surface is, so it drives meshes, cleanup, background estimation and many other tools.
Use it when
- The automatic or aligned threshold does not follow the surface you care about for a particular subject.
Skip it when
- You want the same rule for the whole cohort; use Align Thresholds so the choice is reproducible.
Effect on later steps
- Meshes, cleanup, background tools, landmark snapping (Caution): Intended, just be aware: all of them use this value; changing it changes their results.
Why it works
On the latest edit it sets the subject's main threshold; on an older or elastic edit it stores the value just for that edit.
Signs of poor parameters
- A threshold so low that background noise becomes part of the surface (a fuzzy, bubbly mesh).
- So high that thin structures disappear or break into pieces.
Good parameters
- The lowest value at which the background is free of specks, checked on the thinnest structure you care about.
Finding the best values
- Open the subject's mesh and slices.
- Raise the threshold until the background specks disappear, then check that thin structures are still continuous.
- If both cannot be achieved, consider denoising first rather than compromising the threshold.
How to check the result
- Regenerate the mesh and look at thin and dense regions; compare with a neighbouring subject using its threshold.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root used to locate the subject or atlas JSON. |
| filename | string | required | Subject name, or atlas for the atlas JSON path. |
| edit | string|null | null | Edit stem whose threshold is being saved. Latest non-elastic writes global threshold; any other stem upserts old_thresholds for that edit. |
| threshold | number | required | New threshold stored as int(round(float(threshold))) in metadata. |
Outputs
| Name | Type | Description |
|---|
| threshold | int | Persisted global integer threshold (unchanged when saving a non-latest edit). |
| edit | string | Normalized edit stem that was saved. |
| is_latest | bool | True when the saved edit was the latest non-elastic stem (global write); false when old_thresholds was upserted. |
| old_thresholds | array | Historical per-edit thresholds after the save. |
Algorithm / details
1) Resolve <filename>.json under atlas/ or extracted/<filename>/. 2) Load the JSON metadata. 3) Normalize edit stem and compare to latest non-elastic stem. 4) If latest, set global threshold; otherwise upsert old_thresholds for that stem. 5) ensure_old_thresholds, write the file, return threshold, edit, is_latest, and old_thresholds.
Related: save-threshold-values, apply-threshold
Get Label Names
Keywords: get, label, names
Summary
Returns the project-wide map of label IDs to custom display names from extracted/project_settings.json. Use when multi-label masks need human-readable names shared across every subject in the project.
Considerations
Why use it
Shows readable names for segmentation labels (for example "mandible" instead of Label 3) everywhere in the project.
Use it when
- It runs automatically wherever labels are listed.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root passed to load_project_label_names. |
Outputs
| Name | Type | Description |
|---|
| label_names | object | Map of label id string to custom name; {} if loading fails. |
Algorithm / details
1) Read directory from the POST body. 2) Call load_project_label_names(directory). 3) On any exception, use {}. 4) Return { label_names }.
Related: save-label-names, get-project-settings, mask-out-labels
Save Label Names
Keywords: save, label, names
Summary
Creates or updates a single label's custom display name in the project-wide label map (or clears it when the name is blank). Use when renaming segmentation labels so every scan in the project shows the same names.
Considerations
Why use it
Gives a segmentation label a readable name that every subject in the project shares, which avoids mistakes when transferring, applying or exporting masks.
Use it when
- You start working with multi-label masks, or several people work on the same project.
Why it works
The name is stored once for the project, keyed by label number. A blank name removes it and the interface shows Label n again.
How to check the result
- Open another subject and check that the label shows the new name.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root; mapping is stored in extracted/project_settings.json. |
| label | string|number | required | Label id to rename; required. |
| name | string | | Custom display name; blank removes the custom mapping so the UI falls back to Label <n>. |
Outputs
| Name | Type | Description |
|---|
| label_names | object | Updated full label-name map after the save. |
| error | string | Validation or save failure detail (HTTP 400). |
Algorithm / details
1) Require label in the POST body; read directory and name (default ''). 2) Call save_project_label_name(directory, label, name), which persists into project_settings.json. 3) Return the updated label_names map, or HTTP 400 with error on failure.
Related: get-label-names, save-project-settings, mask-out-labels
Landmarks & Atlas
Random Landmarking
Keywords: random landmarks, Poisson disk, FPS, surface landmarks, symmetry
Summary
Automatically places a requested number of approximately evenly spaced landmarks on a subject surface. Voxel scans marching-cube the edit mesh; preserved meshes load PLY directly. Uses Poisson-disk sampling plus farthest-point refinement, optional symmetry mirroring on mesh projects, snaps landmarks to the surface, writes landmarks and distance JSON, and updates metadata landmark statistics.
Considerations
Why use it
Places a chosen number of evenly spread landmarks on a surface automatically. It is a quick way to get dense, reproducible coverage of a shape, for example to transfer to all subjects or to compare surfaces point by point.
Use it when
- You want dense surface coverage without placing points by hand.
- You plan to transfer the points from the reference or atlas to every subject.
Skip it when
- You need anatomically defined landmarks; random points have no anatomical meaning on their own.
Effect on later steps
- Transfer landmarks (Helps): points placed once on the reference or atlas can be carried to every subject.
Why it works
The surface is sampled with Poisson-disk sampling and refined by farthest-point sampling, which keeps the points evenly spaced. On mesh projects it tries to mirror points across the symmetry plane. Points are then snapped to the surface.
Signs of poor parameters
- Too few points for the shape's detail: small features get no points.
- Far more points than the surface resolution supports: neighbouring points become nearly redundant and transfers become slower.
Good parameters
- A count that puts a few points on the smallest feature you care about; increase until extra points no longer change the coverage visibly.
How to check the result
- Look at the result from several angles: coverage should be even, with no empty regions and no clusters.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject name or atlas. |
| edit | string | | Lossy edit filename; _lossy suffix stripped to locate full-res source. |
| num_landmarks | number | | Target count of landmarks to generate on the surface. |
Algorithm / details
Load mesh from preserved PLY or marching cubes on NIfTI edit, attempt symmetry-plane mirrored landmark warp on mesh projects else Poisson-disk oversample + farthest-point sample to num_landmarks, snap to surface, compute residual and snap distances, save _landmarks.json and _landmark_distances.json, write landmarks summary into subject metadata.
\max_{\mathcal{S}} \min_{i \neq j \in \mathcal{S}} \|x_i - x_j\|
S: selected landmark subset (FPS); x_i: candidate points from Poisson-disk sampling; objective: maximize nearest-neighbor separation.
flowchart TD
A["num_landmarks + edit"] --> B["Build surface mesh"]
B --> C{"Symmetric mesh project?"}
C -->|"yes"| D["Mirror-warp landmarks"]
C -->|"no"| E["Poisson + FPS sample"]
D --> F["Snap to mesh + save JSON"]
E --> F
Related: get-landmarks, upload-landmarks
Get Landmarks
Keywords: landmarks, distances, edit landmarks, atlas landmarks
Summary
Loads landmark coordinates and optional distance arrays for a subject edit. Resolves the correct landmarks JSON stem from the edit name, normalizes list or dict on-disk formats into indexed landmarks with position and landmark_type, and returns paired distances or false when no distance file exists.
Considerations
Why use it
Loads a subject's landmarks and their surface distances so they can be shown and checked in the viewer.
Use it when
- It runs automatically when you open a subject or switch edits.
Why it works
It finds the landmark file that belongs to the edit you are viewing, so landmarks always match the geometry they were placed on.
How to check the result
- If landmarks seem to float off the surface, check that you are viewing the edit they were placed or transferred onto.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject name or atlas. |
| edit | string | | Edit filename used to resolve <edit_stem>_landmarks.json. |
Algorithm / details
Resolve sub_path and landmark edit stem, load landmarks JSON or 422 if missing, convert list entries to indexed dict with landmark_type, load matching _landmark_distances.json when present, return {landmarks, distances}.
flowchart TD
A["filename + edit"] --> B["Resolve landmarks path"]
B --> C{"File exists?"}
C -->|"no"| D["422 not found"]
C -->|"yes"| E["Normalize format"]
E --> F["Load distances if any"]
F --> G["Return payload"]
Related: upload-landmarks, export-landmarks
Upload Landmarks
Keywords: import landmarks, validate, fit, snap to mesh, coordinate mismatch
Summary
Imports external landmark coordinates for a subject edit with validate or fit actions. Validates mean mesh distance against a voxel-size tolerance, optionally runs RANSAC/mesh ICP fit with scaling and snap-to-mesh, then saves landmarks bundle JSON and updates metadata outlier statistics.
Considerations
Why use it
Imports landmarks placed in another program onto a subject, and checks or fixes their alignment to the subject's surface, so external work can join the Aurora pipeline.
Use it when
- You have landmarks from other software or an earlier study for subjects in this project.
Why it works
Validate measures how far the points are from the surface and refuses files that clearly belong to a different pose or scale. Fit first aligns the points to the surface (rigidly, optionally with scaling, then optionally snapping) before saving.
Signs of poor parameters
- Allowing scaling when the external landmarks are in the same units as the scan; it can hide a genuine mismatch.
- Snapping points that are meant to be inside the structure (not on the surface).
Good parameters
- Validate first. Use Fit only when the landmarks belong to the same specimen but a different pose.
- Allow scaling only when the source software used different units.
How to check the result
- After import, check the surface distance statistics: most points should be within about one voxel of the surface.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject name or atlas. |
| edit | string | | Edit filename associated with the uploaded landmarks. |
| landmarks | object | | Indexed landmark dict with position arrays (and optional landmark_type) from the client. |
| action | string | validate | validate checks mesh alignment; fit aligns landmarks to current mesh before save. |
| allow_scaling | boolean | False | When action is fit, permit uniform scaling during landmark-to-mesh alignment. |
| snap_to_mesh | boolean | False | When action is fit, snap fitted landmarks onto the mesh surface. |
| use_outer_shell | boolean | True | Prefer outer-shell mesh for fit when combined with snap_to_mesh. |
Algorithm / details
Load mesh for edit, convert landmarks dict to array, optional fit via landmark_upload_aligner with scaling/snap/outer shell, compute mesh distances and outlier counts, reject with 409/422 on mismatch thresholds, else _save_landmarks_bundle and return success.
\mathrm{mismatch\ if}\ \overline{d} > k \cdot \Delta
d̄: mean landmark-to-surface distance after upload; Δ: subject voxel_size; k = LANDMARK_UPLOAD_MISMATCH_VOXEL_MULTIPLIER = 3 (mismatch when d̄ > 3·Δ).
flowchart TD
A["landmarks + action"] --> B["Load mesh + metadata"]
B --> C{"action == fit?"}
C -->|"yes"| D["ICP/RANSAC fit + optional snap"]
C -->|"no"| E["Validate distances only"]
D --> F{"Within tolerance?"}
E --> F
F -->|"no"| G["409/422 mismatch"]
F -->|"yes"| H["Save landmarks bundle"]
Related: snap-to-mesh, alpaca-align-landmarks-to-mesh, get-landmarks
Transfer landmarks
Keywords: landmark transfer, atlas to subjects, deformation field, propagation, snap
Summary
Propagates landmarks from reference or atlas onto subjects using elastic deformation fields: trilinear interpolation of the 8-neighborhood displacement, optional outer-shell snap, and stored mesh distances for QC. Supports reference→subjects, atlas→subjects (via mean inverse then per-subject forward), and reference→atlas modes. A fourth mode, subject→reference, pulls the landmarks of the selected subject back onto the reference. Faulty scans are skipped, and subjects missing the required elastic deformation data are skipped when encountered. The flag control limits the subject batch modes to flagged subjects or excludes them.
Considerations
Why use it
Places landmarks on every subject automatically by carrying them from the reference or atlas along each subject's elastic deformation. You landmark one specimen and get homologous points on all of them.
Use it when
- Elastic registration has finished and the reference or atlas has landmarks.
- You want to bring a subject's own landmarks back onto the reference (subject to reference).
Skip it when
- Elastic registration is missing or poor for a subject. Transferred points can only be as good as the deformation.
Effect on later steps
- Snap To Mesh (Helps): snapping corrects the small residual offset from the surface after transfer.
- Export landmarks (Helps): the distance files written here let you screen for poorly transferred points.
Why it works
Each landmark is moved by the subject's deformation at that point, interpolated from the eight surrounding voxels. Atlas landmarks first go back to the reference through the average deformation, then out to each subject. Optionally points are snapped to the subject's outer surface.
Signs of poor parameters
- Turning snapping off when the deformation is coarse (fast presets or downsampled fields): points can sit slightly inside or outside the surface.
- Snapping landmarks that are not on the outer surface (internal structures) to the outer shell.
Good parameters
- Snap on, outer shell on, for surface landmarks on bone or skin.
- Prefer the mask surface when the landmarks belong to a segmented structure rather than the thresholded surface.
How to check the result
- Check the snap distances: large moves point to a poor deformation in that region.
- Look at a few subjects with very different shapes; homologous points should sit on the same anatomy.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| reference | string | | Reference subject name driving transfer geometry. |
| transferType | string | | Transfer mode: atlas-to-subjects, reference-to-subjects, or reference-to-atlas. |
| autoSnapToMesh | boolean | | When true, snap transferred landmarks onto target subject mesh surfaces. |
| prefer_mask | boolean | False | Use segmentation mask geometry when resolving transfer targets. |
| mask_label | number | 1 | Label id used when prefer_mask is true. |
| overwriteExistingLandmarks | boolean | True | Replace existing subject landmark files when true; skip subjects that already have landmarks when false. |
| flagFilter | string | off | Batch subject filter: off | exclude | only (flagged scans). |
| use_outer_shell | boolean | True | Use outer-shell mesh extraction when snapping transferred landmarks. |
Algorithm / details
1) Branch on transferType to choose source landmarks and target list; load atlas/reference transforms and fields. 2) Reference→subject: x_{S_i} = x_R + ũ_i(x_R) with ũ_i the forward field trilinearly sampled at x_R (8-voxel neighborhood). 3) Atlas→subject: compose φ_i(φ̄⁻¹(x_A)); reference→atlas uses φ̄(x_R). 4) Optionally snap to the target outer shell (shrink-factor aware for lossy fields), write landmarks + distance JSON, update metadata, skip faulty scans.
\mathbf{x}_{S_i} = \phi_i(\mathbf{x}_R) = \mathbf{x}_R + \tilde{\mathbf{u}}_i(\mathbf{x}_R)
Reference→subject transfer; φ_i forward field on the reference grid; ũ_i(x_R): trilinear interpolation of the discrete displacement u_i at (generally off-grid) landmark x_R.
\mathbf{x}_{S_i} = \phi_i\!\bigl(\bar{\phi}^{-1}(\mathbf{x}_A)\bigr)
Atlas→subject: map atlas landmark x_A to reference by the mean inverse field, then to subject i by φ_i.
flowchart TD
A["Source landmarks"] --> B{"transferType"}
B -->|ref→subjects| C["φ_i at x_R"]
B -->|atlas→subjects| D["φ̄⁻¹ then φ_i"]
B -->|ref→atlas| E["φ̄ at x_R"]
C --> F["Optional snap + distances"]
D --> F
E --> F
Related: create-atlas, get-landmarks, upload-landmarks
Snap To Mesh
Keywords: snap, to, mesh
Summary
Projects a subject's landmarks onto the iso-surface (or outer shell) extracted from the current edit. Use after placing or transferring landmarks that sit slightly off the mesh so surface distances and morphometrics stay meaningful.
Considerations
Why use it
Moves landmarks onto the nearest point of the subject's surface, so points that are slightly off after placement or transfer measure the real surface.
Use it when
- After transferring or importing landmarks, or after placing them on a coarse preview.
Skip it when
- Landmarks that should not lie on the surface (internal points, or points defined on another structure).
Why it works
A surface is built from the current edit (or from a mask label), optionally reduced to its outer shell, and each point moves to the closest surface point. Moves larger than two voxels trigger a warning, because they usually mean the point was not where it should be.
Signs of poor parameters
- Outer shell on for landmarks on inner surfaces: they jump to the outside.
- Snapping when a point's warning says it moved far: the snapped point may land on the wrong structure.
Good parameters
- Outer shell on for external anatomy; mask surface for segmented structures.
How to check the result
- Review any point flagged as moving more than two voxels, and check the snap distance file for outliers.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject id under extracted/, or atlas. |
| edit | string | | Edit basename (lossy suffix stripped). Defaults to {filename}.nii.gz. |
| prefer_mask | boolean | False | When true and a paired .nii.mask.gz exists, snap against the mask==mask_label surface instead of intensity threshold. |
| mask_label | number | 1 | Mask label used when prefer_mask is true. |
| use_outer_shell | boolean | True | When true, ALPACA get_outer_mesh keeps the visible outer surface before snapping. |
Outputs
| Name | Type | Description |
|---|
| message | string | Success confirmation after landmarks and distance metadata are written. |
Algorithm / details
1) Resolve subject path and strip _lossy from edit. 2) Load the edit NIfTI and metadata threshold (or replace the volume with mask==mask_label at threshold 0.5 when prefer_mask). 3) Gaussian-smooth with σ = 0.3, run marching cubes at voxel_size spacing, optionally extract the outer shell. 4) Load {edit}_landmarks.json (preserving landmark_type), snap each position to the closest surface point, and warn when any move exceeds 2·voxel_size (snapping still proceeds). 5) Write updated landmarks, recompute surface/snap distance stats (outliers beyond 1· and 6· voxel_size), and update metadata.landmarks.
p' = \arg\min_{q \in S} \|p - q\|_2
p: landmark; S: marching-cubes (optionally outer-shell) surface; p': snapped position. Warn when ‖p'−p‖ > 2·voxel_size.
flowchart TD
A["Load edit + metadata"] --> B{"prefer_mask?"}
B -->|yes| C["Use mask label surface"]
B -->|no| D["Intensity threshold"]
C --> E["Marching cubes σ=0.3"]
D --> E
E --> F{"use_outer_shell?"}
F -->|yes| G["Outer shell (get_outer_mesh)"]
F -->|no| H["Full MC surface"]
G --> I["Closest-point snap"]
H --> I
I --> J["Save landmarks + distances"]
Related: get-landmarks, upload-landmarks, export-landmark-distances, alpaca-get-outer-mesh
Save Guidepoints
Keywords: save, guidepoints
Summary
Persists user-placed guidepoints for a subject edit as {edit}_guidepoints.json (or deletes the file when an empty list is sent). Guidepoints steer landmark curves and mesh-elastic initialization in the Aurora UI.
Considerations
Why use it
Stores the guidepoints you place on a subject. Guidepoints are rough helper points that steer automatic steps, for example manual rigid alignment or mesh elastic initialisation.
Use it when
- Automatic alignment needs help (very different poses, symmetric shapes) and you place matching guidepoints on the reference and the subject.
Why it works
Points are saved as you placed them, next to the edit they belong to. Sending an empty list deletes them.
How to check the result
- Reopen the subject and confirm the guidepoints are in the same places and the same order as on the reference.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject id or atlas. |
| edit | string | | Edit stem; .nii.gz/.ply/_lossy stripped when present. |
| guidepoints | array | | List of {position:{x,y,z}}. Empty list deletes the guidepoints file. |
Outputs
| Name | Type | Description |
|---|
| message | string | Saved or deleted confirmation. |
Algorithm / details
1) Normalize edit stem and resolve atlas vs extracted/ path. 2) If guidepoints is empty, delete the matching *_guidepoints.json and return. 3) Convert UI objects to [x,y,z] rows. 4) Write JSON beside the edit (or {filename}_guidepoints.json when edit is null). Note: a mesh-distance snap helper exists on the view but is not invoked on the current save path — points are stored as provided.
flowchart TD
A["Receive guidepoints"] --> B{"empty list?"}
B -->|yes| C["Delete *_guidepoints.json"]
B -->|no| D["Write [x,y,z] JSON"]
Related: load-guidepoints, get-semi-landmarks, mesh-elastic-registration
Load Guidepoints
Keywords: load, guidepoints
Summary
Loads guidepoints for the requested edit, falling back through earlier edits when the current edit has none. Returns UI-shaped {index, position} objects and optionally previous_edit_guidepoints for restore prompts.
Considerations
Why use it
Loads a subject's guidepoints, and offers ones from an earlier edit when the current edit has none, so you do not have to place them again after every step.
Use it when
- It runs automatically when you open a subject.
How to check the result
- If guidepoints from an earlier edit are offered, check they still sit on the anatomy: steps such as rotation or cropping move the specimen.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject id or atlas. |
| edit | string | | Edit stem, or latest to resolve the highest edit number. |
Outputs
| Name | Type | Description |
|---|
| guidepoints | array | [{index, position:{x,y,z}}, ...] for the current edit (may be empty). |
| previous_edit_guidepoints | object | Optional {source_edit, guidepoints} when the current edit has none but an older edit or unversioned file does. |
| message | string | Status message. |
Algorithm / details
1) Normalize edit; if edit is latest, pick the highest *_edit_N_* volume/mesh. 2) Prefer {edit}_guidepoints.json, else {filename}_guidepoints.json. 3) Convert stored [x,y,z] rows to {index, position}. 4) If still empty and an edit number is known, search edit N−1 … 0 for *_guidepoints.json, then the unversioned file, and attach previous_edit_guidepoints when found.
flowchart TD
A["Resolve edit / latest"] --> B["Try current guidepoints file"]
B -->|found| C["Return guidepoints"]
B -->|empty| D["Search previous edits"]
D --> E["Optional previous_edit_guidepoints"]
Related: save-guidepoints, get-semi-landmarks
Get Semi Landmarks
Keywords: get, semi, landmarks
Summary
Places sliding semi-landmarks between two mesh endpoints on the outer shell: construct a cutting plane from the chord’s residual PCA, Dijkstra-shortest contour path, arc-length equalization, then snap to the shell (Gunz & Mitteroecker–ready ordered samples). Works on voxel subjects (lossy NIfTI → shell) and preserved PLY meshes (open surfaces use the full mesh; closed meshes use the outer shell).
Considerations
Why use it
Places sliding semi-landmarks evenly along the surface between two landmarks, so curves (ridges, sutures, outlines) can be included in geometric morphometrics.
Use it when
- You capture a curve between two fixed landmarks, as needed for sliding semi-landmark analysis.
Why it works
A cutting plane is built through the two endpoints, following the surface between them. The shortest path along the surface inside that plane is found, and points are spaced evenly along it and snapped to the outer surface.
Signs of poor parameters
- Endpoints so far apart that the shortest surface path takes a different route around the specimen than the curve you mean.
- Too few points for a strongly curved path: the shape of the curve is lost.
Good parameters
- Endpoints on the same feature, and enough points that adding more does not change the drawn curve.
How to check the result
- Look at the curve from several angles: it should follow the intended feature without jumping across gaps.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject id or atlas. |
| edit | string | | Lossy edit basename or latest. |
| start_coordinates | object | | {x,y,z} start landmark in physical space. |
| end_coordinates | object | | {x,y,z} end landmark in physical space. |
| num_semilandmarks | number | | Count of semi-landmarks placed strictly between the endpoints. |
Outputs
| Name | Type | Description |
|---|
| semilandmarks | array | [{x,y,z}, ...] equalized snapped positions (excludes endpoints). |
| message | string | Success summary with count. |
Algorithm / details
1) Load the surface: preserved PLY uses the full open mesh or the outer shell of a closed mesh; voxel subjects still build the outer shell from the lossy NIfTI (σ = 0.3 Gaussian, marching cubes, shared Outer shell extraction (get_outer_mesh), largest component). 2) Sample 100 points on the straight start→end chord; snap each to the shell; remove the component along unit line direction d; take v_1 = first principal component of residuals; set plane normal n = d × v_1. 3) Keep outer-shell vertices within half the specimen voxel spacing of the plane. 4) Dijkstra (scipy.sparse.csgraph) on the plane contour graph for the shortest start→end path. 5) Place num_semilandmarks at equal arc length along that path; project to the plane; snap to shell; return interior points only.
\mathbf{n} = \mathbf{d} \times \mathbf{v}_1
Cutting-plane normal; d: unit start→end direction; v_1: first PC of shell residuals after removing the d component (from 100 chord samples).
t_i = \frac{i}{n+1}\, L,\quad i=1..n
Arc-length targets along the Dijkstra plane–shell path of length L; n = num_semilandmarks.
flowchart TD
A["Outer shell from lossy NIfTI"] --> B["Straight chord samples"]
B --> C["Cutting plane"]
C --> D["Dijkstra plane contour path"]
D --> E["Arc-length equalize"]
E --> F["Project + snap to shell"]
Related: rebalance-landmarks-along-path, alpaca-get-outer-mesh, snap-to-mesh, save-guidepoints
Rebalance Landmarks Along Path
Keywords: rebalance, landmarks, along, path
Summary
Redistributes three or more ordered curve landmarks to equal arc-length spacing along an interpolating cubic spline through their current positions; endpoints stay fixed and middle points snap to the outer shell (paper §2.6.5b).
Considerations
Why use it
Re-spaces an existing curve of landmarks to equal distances along it, keeping the endpoints fixed. Even spacing is what sliding semi-landmark methods expect.
Use it when
- You edited points on a curve by hand, or transferred a curve, and the spacing became uneven.
Why it works
A smooth spline is fitted through the current points, new points are placed at equal arc length along it, and the middle points are snapped to the outer surface. The curve's shape is kept, only the spacing changes.
Signs of poor parameters
- Points given out of order along the curve: the spline loops back on itself.
How to check the result
- The curve should look the same, with evenly spaced points.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject id or atlas. |
| edit | string | | Lossy edit used to build the outer shell context. |
| landmarks | array | | Ordered list of {index,x,y,z}; requires ≥ 3 points. |
Outputs
| Name | Type | Description |
|---|
| landmarks | array | Same indices with updated x,y,z after rebalance + shell snap. |
| message | string | Count of rebalanced landmarks. |
Algorithm / details
1) Validate ≥ 3 landmarks each with index,x,y,z; resolve endpoint order. 2) Reuse GetSemiLandmarksView._load_shell_mesh_context for the outer shell. 3) Fit an interpolating cubic B-spline through all current positions (shape unchanged). 4) Densely sample the spline (max(500, n·200) samples), place points at equal arc-length fractions including endpoints, snap only the middle points to the shell, and return updated coordinates with original indices.
p_i = \gamma\!\left(\frac{i}{n-1}\right),\quad i=0..n-1
γ: interpolating spline through the input landmarks; equal arc-length parameter. Endpoints i=0 and i=n−1 stay put; interiors are snapped to the outer shell.
flowchart TD
A["Ordered landmarks ≥3"] --> B["Interpolating B-spline"]
B --> C["Equal arc-length samples"]
C --> D["Snap middles to outer shell"]
D --> E["Return same indices"]
Related: get-semi-landmarks, snap-to-mesh, save-guidepoints
Export landmarks (file)
Keywords: export, landmarks
Summary
Downloads a project-wide CSV of the latest landmark coordinates per non-faulty subject (Subject plus landmark_*_x/y/z columns, with semi_ prefix for semi-landmarks). One file for morphometric analysis outside Aurora.
Considerations
Why use it
Exports one table of everyone's latest landmark coordinates, ready for morphometric analysis in other software.
Use it when
- You only need coordinates. Use Export landmarks (the bundle) when you also want the quality-control distances.
Why it works
One row per non-faulty subject, three columns (x, y, z) per landmark, semi-landmarks prefixed semi_.
How to check the result
- Check that the number of rows equals the number of non-faulty subjects with landmarks and that no columns are empty.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root containing extracted/. |
Outputs
| Name | Type | Description |
|---|
| (file) | text/csv | Attachment landmarks_export_YYYYMMDD_HHMMSS.csv. |
Algorithm / details
1) List extracted/ subjects; skip missing metadata or faulty=true. 2) For each remaining subject, take the newest *landmarks.json by mtime. 3) Normalize list/dict formats; prefix semi_ when landmark_type is semi. 4) Build a wide table Subject, landmark_i_x/y/z; write a timestamped CSV under .temp/ and return it as a FileResponse.
flowchart TD
A["Scan extracted/"] --> B["Skip faulty"]
B --> C["Latest *_landmarks.json"]
C --> D["Wide CSV rows"]
D --> E["FileResponse"]
Related: export-landmarks-bundle, export-landmarks-json, export-landmark-distances, get-landmarks
Export landmark distances
Keywords: export, ZIP, surface distances, snap distances, QC
Summary
Exports per-landmark mesh distance QC data as a ZIP containing surface-distance CSV and, when available, pre-snap distance CSV. Surface distances reflect landmark-to-mesh residuals; snap distances record how far landmarks moved during snapping.
Considerations
Why use it
Exports how far each landmark is from the surface and how far it moved when snapped. These numbers are your quality control for placement and transfer.
Use it when
- Before analysis, to find landmarks or subjects with poor placement.
How to check the result
- Sort by distance: values well above one voxel usually mark a misplaced or poorly transferred landmark worth checking by eye.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
Algorithm / details
For each non-faulty subject with landmarks, load paired _landmark_distances.json, extract distances and optional snap_distance arrays, build aligned CSV rows with semi_landmark naming, zip surface and snap CSVs into timestamped archive, return ZIP download.
flowchart TD
A["directory"] --> B["Collect distance arrays per subject"]
B --> C["Build surface CSV"]
C --> D{"snap_distance present?"}
D -->|"yes"| E["Add snap CSV to ZIP"]
D -->|"no"| F["ZIP surface CSV only"]
E --> G["Download ZIP"]
F --> G
Related: export-landmarks-bundle, get-landmarks
Export landmarks
Keywords: export, landmarks, bundle
Summary
Downloads a ZIP with coordinates CSV plus surface (and snap) distance CSVs for all non-faulty subjects that have landmarks — the one-click package matching separate coordinate and distance exporters.
Considerations
Why use it
Exports landmark coordinates together with their quality-control distances in one ZIP, so analysis and quality control use the same files.
Use it when
- You hand landmarks over for analysis. It is the recommended export.
Why it works
It writes the same coordinate table as Export landmarks (file), plus surface-distance and snap-distance tables when they exist.
How to check the result
- Open the distance tables and check for outliers before running statistics.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root containing extracted/. |
Outputs
| Name | Type | Description |
|---|
| (file) | application/zip | ZIP with coordinates CSV and optional surface/snap distance CSVs. |
Algorithm / details
1) Same subject filter as ExportLandmarksView. 2) Collect coordinate rows from each latest landmarks file (semi_ naming). 3) When a paired *_landmark_distances.json exists, add surface distance rows and snap_distance rows when present. 4) Zip the CSVs under a timestamped name in .temp/ and return as application/zip.
flowchart TD
A["Per-subject landmarks"] --> B["Coordinates CSV"]
A --> C["Optional distance CSVs"]
B --> D["ZIP FileResponse"]
C --> D
Related: export-landmarks, export-landmarks-json, export-landmark-distances, get-landmarks
Export landmarks (JSON)
Keywords: export, landmarks, json
Summary
Downloads a single JSON object mapping each non-faulty subject to its latest landmark dictionary (landmark_i / semi_landmark_i → [x,y,z]) for interchange with other tools.
Considerations
Why use it
Exports every subject's latest landmarks as one JSON file for tools and scripts that prefer JSON to tables.
Use it when
- You process landmarks in your own code or move them to another tool that reads JSON.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root containing extracted/. |
Outputs
| Name | Type | Description |
|---|
| (file) | application/json | Attachment {project}_landmarks_YYYYMMDD_HHMMSS.json. |
Algorithm / details
1) Sort extracted/ subjects; skip faulty or missing metadata. 2) Load newest *landmarks.json per subject; normalize to a dict with semi_ prefixes when needed. 3) Write {scan_name: {landmark_key: [x,y,z]}} to .temp/ and return as a JSON FileResponse.
Related: export-landmarks, export-landmarks-bundle, get-landmarks
Create atlas
Keywords: atlas, population average, elastic registration, inverse transform, ANTs
Summary
Constructs a population atlas from hub-and-spoke elastic registrations without additional pairwise warps: average forward displacements on the reference grid define atlas geometry, and the mean of intensities pulled into reference space is warped by the inverse mean field. Optional post-step after elastic registration when a population-average target is desired.
Considerations
Why use it
Builds a population-average specimen: the average shape and average intensity of the cohort. It is an unbiased template to landmark once, to segment once, or to show the typical anatomy.
Use it when
- You want to landmark or segment one average specimen and transfer the result to everyone.
- You want a reference that is not biased towards one individual.
Skip it when
- Elastic registration is not finished or is poor for many subjects; the atlas averages their errors.
Effect on later steps
- Make Atlas Reference (Helps): turns the atlas into a reference so you can register everyone to it.
- Transfer landmarks and masks (Helps): atlas-to-subjects mode carries atlas annotations to every subject.
Why it works
Every subject's deformation onto the reference is averaged to get the average shape. Each subject's intensities are pulled into reference space and averaged, and the result is moved into the average shape.
Signs of poor parameters
- Including subjects with failed elastic registrations: the atlas becomes blurred or distorted in those regions.
How to check the result
- The atlas should look sharp where the cohort is consistent and only softly blurred where individuals genuinely differ.
- If a region is very blurred or doubled, check the elastic registrations of the subjects that differ most there.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory with extracted/ subjects. |
| reference | string | | Reference subject name whose space defines the atlas construction frame. |
Algorithm / details
1) Include the reference as identity (u_0 ≡ 0) with the N−1 movers that have elastic_fwd fields; skip faulty subjects. 2) Accumulate displacement vectors u_i on the shared reference grid and form the mean field φ̄(x) = x + (1/N) Σ u_i(x). 3) For each specimen, sample I_i(φ_i(x)) into reference space and average to Ī_ref(x). 4) Numerically invert φ̄ (ANTs invert_displacement_field) and set I_atlas(y) = Ī_ref(φ̄⁻¹(y)). 5) Write atlas intensities, fields, and metadata under atlas/.
\bar{\phi}(\mathbf{x}) = \mathbf{x} + \frac{1}{N}\sum_{i=0}^{N-1}\mathbf{u}_i(\mathbf{x})
Mean forward map on the reference grid; u_i: forward displacement of specimen i (u_0 ≡ 0 for the reference); N: cohort size including the reference.
\bar{I}_{\mathrm{ref}}(\mathbf{x}) = \frac{1}{N}\sum_{i=0}^{N-1} I_i\!\bigl(\phi_i(\mathbf{x})\bigr)
Mean intensity in reference geometry; φ_i(x) = x + u_i(x) pulls subject i into the reference frame.
I_{\mathrm{atlas}}(\mathbf{y}) = \bar{I}_{\mathrm{ref}}\!\bigl(\bar{\phi}^{-1}(\mathbf{y})\bigr)
Atlas intensity at physical coordinate y; φ̄⁻¹ is the numerical inverse of the mean forward field (ANTs invert_displacement_field on the shared grid).
flowchart TD
A["Forward fields φ_i on reference grid"] --> B["Mean displacement φ̄"]
A --> C["Pull intensities → Ī_ref"]
B --> D["Invert φ̄"]
C --> E["I_atlas = Ī_ref(φ̄⁻¹)"]
D --> E
Related: make-atlas-reference, align-to-reference, get-all-subjects-mesh
Make Atlas Reference
Keywords: atlas reference, ReferenceAtlas, promote atlas, selected reference
Summary
Promotes the population atlas into a normal extracted reference subject (ReferenceAtlas): moves atlas/ or legacy extracted/atlas/ into extracted/ReferenceAtlas/, renames atlas-prefixed files, updates project_settings selected_reference, and removes obsolete average-only files. Idempotent when already promoted.
Considerations
Why use it
Turns the atlas into a regular subject (ReferenceAtlas) and makes it the project reference, so you can register every subject to the average specimen.
Use it when
- You want a second registration round to the unbiased average, which usually improves registrations and transfers.
Effect on later steps
- Elastic registration (Caution): Real risk if you transfer without re-registering; the new reference is worth it: existing registrations were made to the old reference; redo them to the new reference before transferring.
Why it works
The atlas folder is moved to extracted/ReferenceAtlas, its files are renamed, and the project reference is set to it.
How to check the result
- Check the reference selector shows ReferenceAtlas and that it opens like any other subject.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing atlas output and extracted/ folder. |
Algorithm / details
If already promoted, refresh settings via finalize_promoted_reference_atlas. Else locate atlas source folder, shutil.move to extracted/ReferenceAtlas, rename files from atlas* to ReferenceAtlas*, set selected_reference in project_settings.json, return paths and cleanup summary.
flowchart TD
A["directory"] --> B{"Already promoted?"}
B -->|"yes"| C["Refresh settings"]
B -->|"no"| D["Move atlas → extracted/ReferenceAtlas"]
D --> E["Rename files + update settings"]
C --> F["Return subject_path + selected_reference"]
E --> F
Related: create-atlas, get-project-settings
Volume Display & Previews
Ensure Quick Grid Projections
Keywords: Quick Grid, batch projection, generate PNG, non-elastic edit, mesh projection
Summary
Batch-generates missing front/back projection PNG pairs for Quick Grid subjects. For each requested scan, resolves the latest non-elastic NIfTI or preserved PLY edit, generates isometric PNGs from lossy or full volume data when absent, or ensures mesh projection PNGs for PLY edits.
Considerations
Why use it
Generates any missing preview images for the subject grid, so the grid shows every subject's current state.
Use it when
- It runs automatically when the grid opens and some previews are missing or outdated.
How to check the result
- If a card still shows an old preview after an edit, reopen the grid; the preview is regenerated from the latest non-elastic edit.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| subjects | object[] | | Each item: scan_name (required), edit (optional hint; latest non-elastic edit is resolved when omitted). |
Algorithm / details
For each subject, resolve latest non-elastic edit via lossy edit, edit glob, original NIfTI, or projection PNG hints. If front/back PNG pair exists, mark ready; else generate from lossy/full NIfTI with uint8 normalization or call ensure_mesh_projection_pngs for PLY. Return per-subject ready, generated, and debug fields.
flowchart TD
A["subjects list"] --> B["Resolve latest non-elastic edit"]
B --> C{"Projection pair exists?"}
C -->|"yes"| D["ready=true"]
C -->|"no"| E{"PLY or NIfTI?"}
E -->|"PLY"| F["ensure_mesh_projection_pngs"]
E -->|"NIfTI"| G["generate_lossy_isometric_projection_png"]
F --> H["Append result"]
G --> H
D --> H
Related: projection-png, get-grid-view
Get Grid View Preview
Keywords: grid preview, thumbnail, histogram, quadrant render, preserved mesh
Summary
Returns a base64 JPEG grid preview and intensity histogram for a subject or atlas, regenerating the thumbnail when the latest non-elastic edit or threshold changed. Voxel scans render four orthogonal/max-projection quadrants from the latest lossy edit; preserved PLY meshes render a mesh-specific preview JPEG.
Considerations
Why use it
Gives each subject's grid card its preview image and intensity histogram, so you can compare subjects at a glance.
Use it when
- It runs automatically in the grid. The histogram is the quickest way to compare intensity scales across subjects.
Why it works
The preview is regenerated only when the latest edit or threshold changed; otherwise the saved preview is reused.
How to check the result
- Use the grid histograms to check Background Offset Correction and Intensity Normalization: after both, the peaks should sit at similar positions in every subject.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| scan_name | string | | Subject folder name or atlas. |
| ignoreElastic | boolean | False | When true, ignore elastic edits when picking the latest edit for preview staleness checks. |
Algorithm / details
Compare preview.json last_edit, threshold, and timestamp against latest edit file. If current, read cached preview.jpg; otherwise load latest lossy NIfTI or preserved mesh PLY, render quadrant JPEG or mesh preview, update preview.json, compute histogram from metadata/threshold, and return base64 image plus histogram_data.
flowchart TD
A["scan_name + directory"] --> B{"Preview up to date?"}
B -->|"yes"| C["Read preview.jpg"]
B -->|"no"| D{"Preserved mesh?"}
D -->|"yes"| E["Render mesh preview"]
D -->|"no"| F["Render lossy quadrant JPEG"]
C --> G["Attach histogram"]
E --> G
F --> G
Related: projection-png, ensure-quick-grid-projections, extract-scans
Mesh Generation & Overlay
Marching Cubes Mesh
Keywords: marching cubes, GLB, Quick Mesh, threshold, heatmap, preserved mesh, shell mode
Summary
Generates the interactive 3D mesh shown in Quick Mesh Canvas. Voxel scans run marching cubes on a lossy or full-res NIfTI at the project threshold (with optional Gaussian blur, shell mode, and label masks); preserved PLY scans load and decimate native mesh geometry. Returns base64 GLB plus center/scale_factor for BabylonJS display, optional base64-encoded per-vertex heatmap distances after elastic registration, and multi-label GLB when segmentations are present.
Considerations
Why use it
Builds the 3D surface you see in the mesh view from the scan at its threshold. It is how you judge thresholds, cleanup, alignment and registration results in 3D.
Use it when
- It runs whenever you view a subject in 3D. High quality and full resolution are worth turning on when you inspect fine detail.
Why it works
The surface where the intensity crosses the threshold is extracted (marching cubes), optionally after a light blur, from the preview or full-resolution volume. It is then simplified for display. After elastic registration it can colour the surface by distance to the reference.
Signs of poor parameters
- Judging fine structures on the default preview mesh: holes or breaks may come from the preview resolution, not the data.
- A large blur for inspection: it hides real noise and thin gaps.
Good parameters
- Preview for quick checks; full resolution and high quality before deciding on thresholds or cleanup.
How to check the result
- Compare the preview mesh with the full-resolution mesh on a thin structure to see what the preview hides.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory. |
| filename | string | | Subject folder name or atlas. |
| edit | string|null | | Lossy edit filename, latest, explicit PLY edit, or null for base lossy/original volume. |
| threshold | number|string | fromfile | Iso-surface level; fromfile reads metadata threshold mapped through lossy compression params. |
| high_quality | boolean | False | When true, skip display decimation so the returned mesh keeps more vertices. Marching-cubes spacing is always voxel_size × resolution_factor (see algorithm math). |
| shell_mode | boolean | False | Extract outer shell surface instead of full solid isosurface. |
| full_resolution | boolean | False | Load full-resolution NIfTI instead of lossy edit. |
| calculate_dense_distances | boolean | False | Compute per-vertex registration heatmap distances for elastic edits. |
| gaussian_blur | number|null | | Gaussian sigma applied to volume before isosurface extraction. |
| mask_label | number|string|null | | When set with use_label_mask, restrict mesh to this label id, or "all" for a multi-label GLB (shared center/scale; heatmap path disabled). |
| use_label_mask | boolean | True | When true and mask_label set, use label mask instead of global threshold. |
| heatmap_metric | string | surface_distance | Registration heatmap metric id; legacy elastic_metric alias accepted. Falls back when unsupported for mesh/voxel combo. |
Algorithm / details
Route PLY or preserved-mesh metadata to decimated GLB loader with optional reference-surface distances. Otherwise load NIfTI, swap axes for BabylonJS, map threshold through lossy params, reject degenerate foreground ratios, apply optional blur/mask/shell, run marching cubes at spacing = voxel_size × resolution_factor, decimate to vertex budget, center and scale vertices, encode GLB and optional float32 distance buffer as base64.
S = \{ v \mid I(v) \ge \tau \}
S: iso-surface; I(v): volume intensity at voxel v; τ: threshold (mapped through lossy params when needed).
\Delta = \delta \cdot r
Δ: marching-cubes spacing; δ: native voxel_size; r: resolution_factor from lossy compression metadata.
flowchart TD
A["POST mesh request"] --> B{"PLY / is_mesh?"}
B -->|"yes"| C["Load/decimate preserved mesh GLB"]
B -->|"no"| D["Load NIfTI + map threshold"]
D --> E{"Valid foreground ratio?"}
E -->|"no"| F["400 threshold error"]
E -->|"yes"| G["marching cubes + decimate"]
G --> H["Center/scale + GLB base64"]
C --> H
H --> I{"calculate_dense_distances?"}
I -->|"yes"| J["Encode heatmap distances"]
I -->|"no"| K["Return payload"]
Related: physically-accurate-mesh, get-all-subjects-mesh, overwrite-threshold
Scan Extraction
Subject QC & Labels
Geometric Edits
Project Settings & Cleanup
Atlas Creation
Landmarks I/O
Preprocessing
Mesh Cleanup
Cleanup mesh
Keywords: mesh, islands, connected components, artifact removal, gaussian blur, threshold
Summary
Removes spurious disconnected foreground islands from a single voxel mesh or atlas volume by thresholding, connected-component labeling, and resetting removed voxels to the estimated background intensity. Useful after segmentation or thresholding when stray blobs, noise islands, or partial connections remain; optional Gaussian pre-smoothing and volume-based island filtering refine results. Rejected when the source edit is from elastic registration.
Considerations
Why use it
Removes stray blobs and floating specks from one subject's segmentation, so its surface is a single clean specimen. Specks distort meshes, measurements, landmark snapping and surface-based alignment.
Use it when
- The mesh shows loose fragments, dust, holder pieces or noise islands around the specimen.
- You are about to snap landmarks, run ALPACA alignment or export a surface.
Skip it when
- The specimen really consists of separate pieces you need (loose teeth, separate bones). Default mode keeps only the largest piece; use the minimum island size option instead.
- The source edit is an elastic registration result; cleanup refuses those.
Effect on later steps
- Align to reference (ALPACA) (Helps): surface matching is not distracted by fragments.
- Snap To Mesh and landmark tools (Helps): points cannot snap onto a speck next to the specimen.
- Background Offset Correction (No difference): removed voxels are set to the background level, so the background estimate is unchanged.
Why it works
Voxels above the threshold are grouped into connected pieces. By default every piece except the largest is set to the background value; with the island-size option, pieces smaller than a percentage of the specimen are removed instead. With a blur set, detection runs on a blurred copy, and above-threshold voxels that vanish under the blur are removed as surface noise too.
Signs of poor parameters
- Largest-piece mode on a specimen made of several real pieces: everything but the biggest piece is erased.
- A minimum island size so high that real small structures are removed.
- A large blur: thin structures disappear in the blurred copy and are removed from the real data as noise.
Good parameters
- Default largest-piece mode for single connected specimens.
- Island-size mode with a small percentage (for example 0.5–2 %) when real separate pieces exist; raise it only until the noise is gone.
- No blur, or a small one (about 0.5–1 voxel), when the surface has fine speckle noise.
Finding the best values
- Run it on one subject and compare the mesh before and after.
- If real pieces disappeared, switch to island-size mode and lower the percentage.
- If specks remain, raise the percentage a little, or add a small blur.
- Use the same settings for the whole cohort with Batch cleanup mesh.
How to check the result
- Compare the mesh before and after from several angles, paying attention to thin structures and small separate parts.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted or atlas scan folders. |
| filename | string | | Scan or atlas base name; threshold is read from its metadata JSON. |
| edit | string|null | | Source NIfTI edit filename to clean; null uses the base scan file. |
| gaussian_blur | number|null | | Gaussian sigma applied before island detection; 0 or null disables smoothing and post-noise cleanup. |
| use_island_volume_threshold | boolean | False | When true, keep all islands above a minimum volume fraction instead of keeping only the largest component. |
| min_island_volume_percent | number | 1.0 | Minimum island size as a percentage of total foreground voxels; clamped to 0.1–30.0. |
Algorithm / details
Load the latest or specified NIfTI edit and read threshold from metadata. Optionally Gaussian-smooth a copy for island detection. Estimate background as the mode (or mean fallback) of subsampled voxels below threshold. Label connected components where smoothed data exceeds threshold. In default mode, remove every component except the largest by setting those voxels to the background value. In volume-threshold mode, remove components smaller than min_island_volume_percent of foreground. If Gaussian blur was used, also remove voxels above threshold in the original data that fall below threshold in the smoothed volume. Save a new *_cleaned edit plus lossy copy, and propagate masks and landmarks.
b = \mathrm{mode}\{ v \mid v < \tau \}
b: estimated background intensity; v: subsampled voxel values; τ: threshold from metadata.
\mathcal{I} = \{ v \mid (G_\sigma * I)(v) > \tau \}
I: island mask; G_σ: optional Gaussian smooth; σ: blur scale; τ: threshold.
\mathrm{keep} = \arg\max_C |C|\ \mathrm{or}\ \{ C : |C| \ge p\% \cdot |F| \}
C: connected component; |C|: component size; F: foreground; p = min_island_volume_percent (default 1.0, clamped to 0.1–30) in volume-threshold mode.
v_{\mathrm{removed}} \leftarrow b
Removed island voxels are replaced with background b.
flowchart TD
A["Load NIfTI + threshold"] --> B{"Elastic edit?"}
B -->|"yes"| X["Reject request"]
B -->|"no"| C["Optional Gaussian blur"]
C --> D["Estimate background below threshold"]
D --> E["Label connected components"]
E --> F{"Multiple islands?"}
F -->|"no"| G["No cleanup"]
F -->|"yes"| H{"Volume threshold mode?"}
H -->|"no"| I["Keep largest island"]
H -->|"yes"| J["Keep islands ≥ min volume %"]
I --> K["Set removed voxels to background"]
J --> K
K --> L{"Gaussian used?"}
L -->|"yes"| M["Remove blur-only noise voxels"]
L -->|"no"| N["Save cleaned edit + lossy"]
M --> N
Related: batch-cleanup-mesh, apply-threshold, homogenize-background, apply-mesh-cleanup
Batch cleanup mesh
Keywords: batch, mesh cleanup, project-wide, preserved mesh, voxel mesh, islands
Summary
Runs mesh cleanup across all eligible scans in a project directory. Voxel-based scans use the same island-removal logic as Cleanup Mesh; preserved PLY meshes are routed to the preserved-mesh cleanup path. Skips elastic edits, already-cleaned edits, scans with no removable islands, and scans filtered by flag settings. Returns per-scan success, skip, or error results. Voxel scans can be Gaussian-blurred before island cleanup; with a blur set, above-threshold voxels that disappear after blurring are also removed as surface noise. Preserved meshes use connected-component cleanup on the surface. The island-size controls apply to both paths. Faulty scans are skipped.
Considerations
Why use it
Removes stray blobs and surface noise from every subject in one run, so all surfaces are clean before surface-based alignment, landmarking and export.
Use it when
- Several subjects show floating fragments or noise around the specimen.
- Before ALPACA alignment, landmark snapping or surface export for the cohort.
Skip it when
- Specimens contain real separate pieces and you have not switched to island-size mode.
- Subjects already have an elastic registration; they are skipped.
Effect on later steps
- Align to reference (ALPACA) (Helps): surface matching is not distracted by fragments.
- Landmark snapping and transfer (Helps): points cannot snap onto a speck next to the specimen.
Why it works
Each voxel subject is cleaned exactly like Cleanup mesh; preserved meshes have their small disconnected surface pieces removed. A subject is only saved when something was actually removed, so clean subjects keep their current edit.
Signs of poor parameters
- Settings chosen on one subject without checking a subject with small real parts.
- A large blur, which can remove thin real structures as noise.
Good parameters
- The settings that worked on one or two representative subjects with Cleanup mesh, applied to everyone.
Finding the best values
- Tune on the subject with the most noise and on the one with the finest structures using Cleanup mesh.
- Use the pair of settings that cleans the noisy one without damaging the fine one.
- Run the batch, flagging subjects that need different settings to rerun them separately.
How to check the result
- Scroll through the grid previews: specimens should be whole, with no floating fragments and no missing parts.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory whose extracted subfolders are processed. |
| gaussian_blur | number|null | | Gaussian sigma forwarded to voxel mesh cleanup; null disables blur. |
| use_island_volume_threshold | boolean | False | When true, keep islands above the minimum volume fraction instead of only the largest island. |
| min_island_volume_percent | number | 1.0 | Minimum retained island volume as a percentage of foreground; clamped to 0.1–30.0. |
| flagFilter | string | off | Subject flag filter: off, exclude flagged scans, or only flagged scans. |
| propagate_linked | boolean | True | When true, after cleanup removes islands on a main scan, apply the same removal mask to same-shape linked siblings. |
Algorithm / details
Enumerate non-faulty extracted scans, drop voxel scans with elastic registration, apply flag filter, skip linked children up front, then iterate. For preserved meshes, delegate to ApplyMeshCleanupView with latest edit. For voxel scans, load latest non-elastic, non-cleaned edit, run CleanupMeshView.clean_mesh, and save only when islands were actually removed. Emit WebSocket progress updates and aggregate results. When propagate_linked and islands were removed, replay the removal mask onto same-shape linked siblings (results may include linked_propagation).
flowchart TD
A["List extracted scans"] --> B["Apply flag filter"]
B --> C{"For each scan"}
C --> D{"Preserved PLY mesh?"}
D -->|"yes"| E["Preserved mesh cleanup"]
D -->|"no"| F{"Elastic or already cleaned?"}
F -->|"yes"| G["Skip"]
F -->|"no"| H["Run clean_mesh"]
H --> I{"Islands removed?"}
I -->|"no"| G
I -->|"yes"| J["Save cleaned edit + lossy"]
E --> K["Collect result"]
G --> K
J --> K
Related: cleanup-mesh
Weld mesh
Keywords: weld, proximity weld, preserved mesh, connected components, semi-landmarks, photogrammetry, batch
Summary
Proximity-welds near-touching vertices across different connected components on preserved PLY meshes, writing a _welded edit and mesh_weld_settings provenance. Exposed in the Restore tooltip as Weld mesh (appears only when the project has at least one preserved mesh). Skips voxel volumes, already-welded latest edits, and subjects with no cross-component pairs within the distance threshold. Propagates built-in face-corner UVs via the standard mesh-edit remap path. Welding makes fragmented surfaces path-connected, which semi-landmarks and other surface tools need. Faulty scans are skipped.
Considerations
Why use it
Joins nearly touching pieces of a preserved mesh into one connected surface. Many surface scans and exported meshes are split into pieces that only look connected, and semi-landmarks, outer-shell extraction and other surface tools need paths across them.
Use it when
- Semi-landmark curves stop at seams or jump, or the outer shell misses parts of the surface.
- A mesh is reported as many components although it looks like one surface.
Skip it when
- The project has only voxel volumes.
- The mesh is already a single connected surface.
Effect on later steps
- Get Semi Landmarks (Helps): curves can cross former seams.
- Outer shell extraction (Helps): the shell is computed on one connected surface instead of on fragments.
Why it works
Vertices on different pieces that are closer than the weld radius (a multiple of the mesh's typical edge length) are merged into one vertex. Vertices on the same piece are never merged, so the surface's own detail is not collapsed.
Signs of poor parameters
- A large weld radius: separate structures that are merely close (for example opposing teeth, or the two sides of a narrow gap) get fused.
- A radius below the gap size: nothing is welded.
Good parameters
- The default (1 × the typical edge length), raised in small steps only if seams remain.
Finding the best values
- Weld one subject at the default radius.
- If seams remain, delete the welded edit and try a slightly larger radius.
- Stop at the smallest radius that removes the seams.
How to check the result
- Draw a semi-landmark curve across a former seam, and inspect narrow gaps to make sure they were not closed.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root whose extracted preserved-mesh subjects are processed. |
| distance_factor | number | 1.0 | Weld radius as a multiple of median edge length (clamped 0.25–5.0). Only pairs on different components are merged. |
| onlyCurrentScan | boolean | False | When true, restrict welding to selectedScan. |
| selectedScan | string|null | | Scan folder name when onlyCurrentScan is true. |
| flagFilter | string | off | Subject flag filter: off, exclude flagged scans, or only flagged scans. |
Algorithm / details
1) List preserved-mesh subjects (is_mesh && !voxelized), apply flag filter, optional single-scan mode. 2) Skip if latest edit already contains welded. 3) Load latest PLY, coincident-weld soup if needed, label connected components. 4) Find vertex pairs within distance_factor × median edge length that lie on different components; union-find merge those pairs and average positions. 5) Drop degenerate/duplicate faces, save {scan}_edit_{n}_welded.ply, remap built-in UVs, copy landmarks, write mesh_weld_settings. 6) Deleting the welded edit clears mesh_weld_settings when no welded edit remains (EDIT_PROVENANCE_CLEANUP).
\varepsilon = f \cdot \mathrm{median}(\ell_e)
ε is the proximity threshold; f is distance_factor; ℓ_e are triangle edge lengths. Only pairs with different component labels and ‖vi−vj‖≤ε are unioned.
flowchart TD
A[List preserved meshes] --> B[Flag filter]
B --> C{Latest already welded?}
C -->|yes| D[Skip]
C -->|no| E[Label components]
E --> F[Cross-component pairs within eps]
F --> G{Any pairs?}
G -->|no| D
G -->|yes| H[Union-find merge + save _welded.ply]
Related: restore-all-scans, batch-cleanup-mesh, apply-mesh-cleanup
Background
Background Offset Correction
Keywords: background, backfixed, intensity offset, batch, threshold shift, Set Background Value, background peak, distance transform, surrounding air, surrounding tissue
Summary
Background Offset Correction subtracts one background level per scan (or a shared manual value) so backgrounds harmonize near zero (values below the estimate clip to the dtype floor), then shifts stored thresholds by the same offset. The automatic estimate is the peak of the lightly smoothed below-threshold intensities away from the specimen edge and from constant padding: both modes split the below-threshold space at one distance from the specimen (the median distance of the exterior): 'Surrounding air' uses the far side (an outer margin measured from the specimen), 'Surrounding tissue' uses the near band, inside or outside the specimen. The subtracted level acts as a baseline landmark shared across specimens.
Considerations
Why use it
Anchors the low end of every scan at a physical landmark: the background (air or mounting medium) is moved to zero, and everything darker, which is noise and outliers, is clipped. Scanners and reconstructions add a baseline that changes between sessions, and subjects differ in size, in how much anatomy they contain and in noise. The background peak is the one level that means the same thing in every subject, so zero ends up meaning "background" everywhere. That is reliable, predictable and easy to check by eye, and it is what makes the later intensity-based steps line up.
Use it when
- As a standard step before rigid (ANTs) and elastic registration. Both rescale each image by its own minimum and maximum before matching, so a stable minimum is what lets the two images line up on the same scale.
- Before Intensity Normalization. Percentiles alone cannot place the lower cut at the same anatomical level in every subject, because subjects differ in size, in how much anatomy is in the field of view and in noise. With the background anchored at zero, the low end is already fixed by a landmark.
- The background has a visibly different grey level between scans under the same display window.
- You plan to copy one threshold to every scan (Align Thresholds by Same Threshold), which needs a shared baseline.
- You want one display window to work for the whole cohort, or to compare grey values between subjects.
Skip it when
- The background already sits at zero in every scan, with nothing darker below it.
- The specimen fills almost the whole field of view or touches the edges, leaving little true background. The automatic estimate then has very few voxels to work with (below 2,000 it falls back to a simpler histogram peak) and can be unreliable. Use Set Background Value with a value you measured yourself instead of skipping the step.
Effect on later steps
- Align to reference (Helps): ANTs rigid alignment rescales each image to [-1, 1] by its own min and max. Without correction the minimum is set by whatever dark noise or outlier each scan happens to contain; after correction it is the background in both, so the images are matched on the same scale.
- Elastic registration (Helps): for the same reason: the same min–max rescaling to [-1, 1] happens before SyN, so a common zero gives fixed and moving images a consistent low end.
- Intensity Normalization (Helps): it fixes the low end with a landmark, so Percentile Normalization only has to decide the upper cut, the one that actually needs per-dataset judgement.
- Align Thresholds (Helps): Same Threshold copies the reference value as is, which only works on a shared baseline.
- Meshes and masks (No difference): the stored threshold moves by the same amount as the voxels, so the segmented shape is unchanged.
Why it works
The tool estimates one background level per scan and subtracts it from every voxel. It then subtracts the same amount from the stored threshold, so the segmentation does not move. Values pushed below the bottom of the data type range are clipped there. For unsigned data (the usual case) that bottom is zero, so the background peak lands at zero and the darker half of the background noise, together with any dark outliers, is clipped to exactly zero. That clipping is the point: it removes the low-end outliers and gives every subject the same floor. Signed data keeps its negative range, so the background centres near zero instead.
The automatic estimate is the most common grey value of lightly smoothed below-threshold voxels. Voxels close to the specimen edge and constant padding (for example from reorientation) are ignored. Surrounding air uses the far exterior, beyond the median distance from the specimen. Surrounding tissue uses the band closer to the specimen, which suits scans where the threshold is set for bone and the soft tissue around it should become the baseline.
Signs of poor parameters
- After correction, the background of some scans is still clearly brighter or darker than the others. The estimate probably locked onto a container wall, a mounting medium or a fixative layer instead of air.
- Dark tissue turned completely black. The subtracted level was too high, usually because the estimate landed on tissue (Surrounding tissue chosen where there is plenty of air, or a specimen that fills the field of view).
- One scan's subtracted value (backfixed_shift in its metadata) is far from the rest of the cohort.
Good parameters
- Surrounding air when every scan has a clear margin of air or medium around the specimen. This is the default and the safer choice.
- Surrounding tissue when the threshold is set for bone and the soft tissue should be the shared baseline, or when there is little air around the specimen.
- A manual value (Set Background Value) when the automatic estimate is unstable. Measure it on a background region of a representative scan and use one value for the cohort.
Finding the best values
- Show several scans with the same display window and compare their backgrounds. If they already match, you can skip this tool.
- Choose the region (Surrounding air or Surrounding tissue) using the cases above.
- Run the batch, then compare the subtracted values stored in each scan's metadata (backfixed_shift). They should cluster; check any outlier by eye.
- For outliers, delete that scan's backfixed edit and rerun it on its own with a manual value.
How to check the result
- Show the subjects with the same display window. The backgrounds should look the same, close to black.
- On the histogram, the tissue peaks should have shifted without changing shape. For unsigned data, also expect a spike at zero holding roughly half of the background voxels.
- Drag the min orb of the intensity slider and hold Shift: cyan should cover background only, never tissue.
- A mesh preview should be unchanged, because the threshold moved by the same amount as the voxels.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| background_value | number|null | | Fixed background intensity to subtract from all scans; null triggers per-scan automatic estimation. |
| background_region | string | outside | Automatic estimation only: 'outside' (Surrounding air, exterior voxels beyond the median exterior distance to the specimen) or 'surrounding_tissue' (Surrounding tissue, any below-threshold voxel between the edge guard and that distance). |
| flagFilter | string | off | Subject flag filter: off, exclude flagged scans, or only flagged scans. |
Algorithm / details
Collect eligible voxel scans. In automatic mode, for each scan: sample the volume with stride s (>= 2, chosen so at most 16M voxels are analysed); mark the specimen as samples >= threshold on unblurred data; mark constant fill (below-threshold voxels whose 3x3x3 neighbourhood holds one value, e.g. reorientation padding) as non-data; compute Euclidean distance transforms to the specimen and to fill and keep below-threshold voxels at least 3 sigma from both; split them at D*, the median specimen distance of the eligible exterior voxels (exterior = not enclosed by the hole-filled specimen): 'outside' keeps exterior voxels with d >= D*, 'surrounding_tissue' keeps any voxel with d < D* (inside/outside is not used, since a bone threshold can leave the body open); Gaussian-blur the samples (sigma = 1 sampled voxel) and take the peak of a Freedman-Diaconis histogram of the selected blurred values, refined by a three-point parabolic fit. If fewer than 2000 voxels qualify, the original sub-threshold histogram mode is used. The method, region, constants, sample count and any fallback are stored in background_offset_settings.estimator. Then subtract the chosen background from every voxel with clipping to dtype bounds, save *_backfixed edits and lossy copies, subtract the same amount from metadata threshold, record backfixed_shift, and copy masks and landmarks unchanged.
b = \arg\max_{x}\, H\big(\{ (G_\sigma * I)(p) \mid p \in R,\ I(p) < \tau,\ D_F(p) \ge 3\sigma,\ D_P(p) \ge 3\sigma \}\big)
b: background peak; I: sampled intensity; G_σ: Gaussian blur (σ = 1 sampled voxel); τ: threshold; D_F, D_P: Euclidean distance to the specimen (I ≥ τ) and to constant padding; R: Surrounding air = exterior with D_F ≥ D* (median exterior distance), Surrounding tissue = any voxel with D_F < D*; H: Freedman–Diaconis histogram, peak refined by a parabolic fit.
v' = \mathrm{clip}(v - b)
v': background-corrected intensity, clipped to the dtype range.
\tau' = \tau - b
τ': threshold shifted by the same background offset so segmentation stays consistent.
flowchart TD
A["List eligible voxel scans"] --> B{"background_value provided?"}
B -->|"no"| C["Phase 1: distance-guarded background peak per scan"]
B -->|"yes"| D["Use fixed background for all"]
C --> E["Phase 2: subtract background"]
D --> E
E --> F["Clip to dtype range"]
F --> G["Save backfixed edit + lossy"]
G --> H["Update threshold and backfixed_shift"]
Related: save-background-values, remove-background, apply-threshold
Remove Background
Keywords: background removal, backremoved, minimum value, batch, threshold
Summary
Sets all voxels below each scan's metadata threshold to that scan's global minimum intensity, producing a flattened background without changing geometry. Processes all eligible voxel scans except the reference scan and scans already backremoved or elastically registered. Useful when a uniform low background is desired for visualization or downstream intensity handling while preserving foreground values. Batch mode also skips preserved PLY meshes and faulty scans.
Considerations
Why use it
Flattens everything below each scan's threshold to the scan's minimum value, leaving only the specimen. A uniform background removes distracting noise and objects for viewing and for steps that should only see the specimen.
Use it when
- Background values are variable and noisy between or within scans.
- You want registration, display or measurements to see only the specimen.
Skip it when
- The threshold does not yet follow the specimen surface; everything below it is erased, including any tissue under the threshold.
- You need soft tissue or other structures that sit below the threshold (for example a bone threshold on a whole-body scan).
Effect on later steps
- Background Offset Correction (Caution): Real risk, easy to avoid: run it before Remove Background; afterwards there is no natural background left to estimate.
- Elastic registration (Caution): A trade-off, often worth it on noisy backgrounds: intensity metrics then only see the thresholded specimen, which can help with noisy backgrounds but removes soft-tissue context.
Why it works
Every voxel below the scan's stored threshold is set to the scan's lowest value. Voxels above the threshold keep their values, and geometry, masks and landmarks are unchanged. The reference scan is not processed.
Signs of poor parameters
- A threshold that is too high: parts of the specimen fall below it and are erased.
Good parameters
- Thresholds checked (and aligned across the cohort) before running it.
How to check the result
- Look at slices through the specimen edge: tissue you care about should be intact, and the background a uniform dark level.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| reference | string|null | | Reference scan name excluded from processing. |
| flagFilter | string | off | Subject flag filter: off, exclude flagged scans, or only flagged scans. |
| propagate_linked | boolean | True | When true, reuse each main scan’s removal mask on same-shape linked siblings. |
Algorithm / details
Skip linked children when enumerating. For each eligible main scan, load the latest edit, read threshold from metadata, compute the volume minimum, assign min_value to all voxels strictly below threshold, save a new *_backremoved edit and lossy copy, and copy paired masks and landmarks without geometric change. When propagate_linked, apply the same removal mask to same-shape linked siblings.
v' = \begin{cases} \min(I) & v < \tau \\ v & \mathrm{otherwise} \end{cases}
v: input intensity; τ: threshold; values below τ are floored to the volume minimum (effectively cleared).
flowchart TD
A["List voxel scans except reference"] --> B["Skip backremoved / elastic scans"]
B --> C["Apply flag filter"]
C --> D["Load latest edit + threshold"]
D --> E["min_value = min('volume')"]
E --> F["Set v < threshold to min_value"]
F --> G["Save backremoved edit + lossy"]
Related: homogenize-background, apply-threshold
Save Background Values
Keywords: background, manual correction, backfixed, per-scan, UI values
Summary
Applies user-specified per-scan background offsets from the UI, subtracting each value from the corresponding volume and updating metadata thresholds. Scans with background 0 are skipped. Each corrected scan is saved as a new *_backfixed edit with lossy copy, adjusted threshold, and backfixed_shift. Useful when automatic background estimation needs manual refinement for individual subjects.
Considerations
Why use it
Applies background levels you set by hand per subject, for subjects where the automatic background estimate was wrong.
Use it when
- A few subjects have a container wall, mounting medium or very little air, and Background Offset Correction picked the wrong level for them.
Skip it when
- The automatic estimate worked; manual values are harder to reproduce.
Effect on later steps
- Same steps as Background Offset Correction (Helps): the result is the same kind of backfixed edit, with the threshold shifted by the same amount.
Why it works
Each given value is subtracted from that subject's latest edit, with the same clipping and threshold shift as Background Offset Correction. Subjects with a value of 0 are left alone.
Signs of poor parameters
- A value read from a tissue region instead of the background: dark tissue is clipped to the floor.
Good parameters
- The most common grey value of plain air or medium away from the specimen and the container, read from the histogram or slices.
How to check the result
- Compare the corrected subject with its neighbours under one display window: backgrounds should match.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| values | object | {} | Map of scan_name to objects containing a numeric background field to subtract. |
Algorithm / details
Filter values to scans that include a background field and skip zero backgrounds. For each scan, load the latest edit, subtract the provided background using HomogenizeBackgroundView._homogenize_background, save *_backfixed output, reduce metadata threshold by the same amount, store backfixed_shift, generate lossy data, and copy masks and landmarks.
v' = \mathrm{clip}(v - b_s)
b_s: saved per-scan background; v': corrected intensity clipped to dtype bounds.
\tau' = \tau - b_s
Threshold is shifted with the same per-scan background.
flowchart TD
A["Receive per-scan background map"] --> B["Skip background = 0"]
B --> C["Load latest edit"]
C --> D["Subtract background"]
D --> E["Save backfixed edit + lossy"]
E --> F["Update threshold and backfixed_shift"]
Related: homogenize-background
Intensity and Threshold
Align Thresholds
Keywords: threshold, align thresholds, Otsu, Li, Kapur, histogram matching, percentile matching, reference scan
Summary
Align Thresholds computes and writes new segmentation thresholds into scan metadata without modifying voxel data. Supports copying a reference threshold, histogram-peak matching, percentile matching, and per-scan automatic methods (Otsu, Li, Kapur entropy). Can target all eligible voxel scans or only the selected scan, excluding preserved meshes, faulty scans, elastic registrations, and optionally the reference itself. Useful for harmonizing mesh extraction thresholds across a cohort before cleanup or registration.
Considerations
Why use it
Gives every scan a threshold chosen by the same rule, so the specimen surface (and everything built on it) is defined consistently across the cohort. The voxel data does not change.
Use it when
- Scans have different intensity ranges but similar anatomy, and tissue boundaries should be chosen consistently across the cohort.
- Before meshing, cleanup, landmarking or ALPACA alignment of a cohort.
Skip it when
- You set each threshold carefully by hand and are happy with them.
Effect on later steps
- Meshes, cleanup, background tools, landmark snapping (Helps): all of them read the threshold, so consistent thresholds give comparable surfaces.
Why it works
Same Threshold copies the reference value. Histogram Matching shifts the reference threshold by the difference between the scans' histogram peaks. Percentile Matching finds the threshold's rank in the reference and uses the same rank in each scan. Otsu, Li and Kapur choose a threshold from each scan's own histogram, without the reference.
Signs of poor parameters
- Same Threshold on scans whose backgrounds or intensity scales differ (no Background Offset Correction or normalization): the surface lands at different tissue in each scan.
- Percentile Matching on scans cropped very differently or with very different specimen sizes: the same rank then means different tissue.
- Automatic methods (Otsu, Li, Kapur) on scans with several tissues of similar size: they may split between the wrong pair of tissues.
Good parameters
- Same Threshold after Background Offset Correction and Intensity Normalization, when scans are on a common scale.
- Histogram Matching when scans differ mainly by a brightness shift.
- Otsu or Li as a starting point when there is no trustworthy reference threshold.
Finding the best values
- Set a good threshold on the reference by eye.
- Run Align Thresholds with the method that fits your data on the current scan only, and look at its mesh.
- If it fits, run the batch; otherwise try another method.
How to check the result
- Open the grid and a few meshes: the same structures should be present at similar thickness in every subject.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| reference | string|null | | Reference scan name; required for same-threshold, histogram-matching, and percentile-matching methods. |
| method | string | same-threshold | Threshold strategy: same-threshold, histogram-matching, percentile-matching, otsus-method, li-method, or kapur-entropy. |
| onlyCurrentScan | boolean | False | When true, update threshold only for selectedScan. |
| selectedScan | string|null | | Scan name to threshold when onlyCurrentScan is true. |
| flagFilter | string | off | Subject flag filter: off, exclude flagged scans, or only flagged scans. |
Algorithm / details
Load eligible voxel target scans (in batch mode, skip linked children) and, for reference-based methods, Gaussian-smooth the reference volume and derive reference threshold, foreground volume, and variance. For each target scan, compute a threshold by method: copy reference value, shift reference threshold by histogram peak difference, match reference percentile rank, or run Otsu/Li/Kapur on smoothed scan data. Clamp below the scan minimum when needed, persist threshold to metadata JSON, and emit progress updates.
T_{\mathrm{scan}} = T_{\mathrm{ref}}
same-threshold: copy the reference threshold onto each target scan.
T_{\mathrm{scan}} = P_{\mathrm{scan}}\!\left(\mathrm{rank}(T_{\mathrm{ref}}; I_{\mathrm{ref}})\right)
percentile-matching: map the reference threshold’s percentile rank onto the target intensity distribution P_scan.
T_{\mathrm{scan}} = T_{\mathrm{ref}} - (p_{\mathrm{ref}} - p_{\mathrm{scan}})
histogram-matching: shift by the difference of intensity histogram peaks p_ref and p_scan.
T^\star = \arg\max_T \left( H_{\mathrm{bg}}(T) + H_{\mathrm{fg}}(T) \right)
Kapur: choose T maximizing background + foreground Shannon entropy.
flowchart TD
A["Select method"] --> B{"Needs reference?"}
B -->|"yes"| C["Load reference + threshold"]
B -->|"no"| D["Per-scan analysis only"]
C --> E["For each target scan"]
D --> E
E --> F{"method"}
F -->|"same-threshold"| G["Copy reference threshold"]
F -->|"histogram-matching"| H["Shift by peak difference"]
F -->|"percentile-matching"| I["Match percentile rank"]
F -->|"otsus/li/kapur"| J["Auto-threshold scan"]
G --> K["Write metadata threshold"]
H --> K
I --> K
J --> K
Related: save-threshold-values, match-histogram, cleanup-mesh, marchingcubes, homogenize-background
Intensity Normalization
Keywords: intensity normalization, histogram matching, peak align, tissue aware, percentile normalize, reference scan
Summary
Normalizes voxel intensities across a project so subjects share a comparable intensity scale (shown in the Aurora UI as Intensity Normalization). Strategies include full foreground histogram matching to a reference, linear threshold alignment, tissue-aware soft-tissue and bone scaling, or standalone percentile clipping and rescaling. Saves new *_histmatched edits, updates thresholds in metadata, and skips preserved meshes, faulty scans, already histmatched scans, and elastic registrations. Useful before registration when scanners or reconstructions produce different intensity profiles.
Considerations
Why use it
Puts every scan on a comparable intensity scale, so the same grey value means roughly the same tissue in every subject. Scanner, session, reconstruction settings, staining and specimen size all shift and stretch the histogram; this tool removes most of that difference so later steps that read raw grey values treat all subjects alike. Percentile Normalization is the recommended mode; the other three modes match each scan to the reference scan.
Use it when
- After Background Offset Correction. That step fixes the low end at the background landmark in every subject; Percentile Normalization then mainly sets the upper cut. Percentiles alone cannot place the low cut at the same anatomical level in every subject, because subjects vary in size, in how much anatomy is in the field of view and in noise.
- You plan to train or fine-tune an AI segmentation model on these scans. AI fine-tuning rescales each volume by its own minimum and maximum, so a few very bright voxels (metal, beam-hardening streaks, dense debris) squeeze all tissue into a narrow grey range. Percentile Normalization clips those outliers first and gives the model the same contrast in every subject.
- You will run voxel elastic registration with the Mean squares or Demons metric. These compare grey values voxel by voxel and assume the same tissue has the same value in both scans.
- Scans come from different scanners, sessions or reconstruction settings, or look clearly brighter or darker than each other under the same display window.
- You want one display window, or one intensity-based measurement, to mean the same thing across the cohort.
Skip it when
- All scans were acquired and reconstructed with identical settings and already look alike under one display window. Normalizing then mostly adds another edit.
- Your analysis needs the scanner's calibrated values (for example Hounsfield units or mineral density). Every mode changes voxel values, and Percentile Normalization stretches each scan by a different amount. Earlier edits are kept, so measure on those instead.
- Your next steps only use the segmentation threshold or the mesh (meshing, rigid alignment, landmarking). The stored threshold is mapped through the same transform, so the segmented shape stays essentially the same.
- The project contains only preserved PLY meshes. The tool works on voxel volumes only.
Effect on later steps
- AI segmentation fine-tuning (Helps): consistent contrast across training subjects, and outliers no longer set each volume's min–max scale.
- Elastic registration (Helps): most with Mean squares or Demons. With the default CC (neighbourhood cross-correlation) the effect is smaller, because CC ignores local brightness and contrast differences. Every metric benefits when outliers are clipped: registration rescales each image by its own min and max to [-1, 1] before matching, and a single very bright voxel compresses all tissue into a small part of that range.
- Create atlas (Helps): the atlas averages intensities across subjects, so mismatched scales bias the average towards the brightest subjects.
- Align to reference (Helps): ANTs rigid alignment also rescales each image to [-1, 1] by its own min and max, so clipping the bright outliers keeps tissue on the same scale in both images. Background Offset Correction does the same for the low end.
- Meshes, masks and landmarks (No difference): the threshold is remapped with the voxels, and masks and landmarks are copied to the new edit unchanged.
Why it works
Percentile Normalization picks two grey values in each scan: the value below which the low percentage of all voxels fall, and the value below which the high percentage fall. Everything darker than the first becomes the minimum (black), everything brighter than the second becomes the maximum (white), and the range in between is stretched in a straight line over the whole data type range (0–65535 for unsigned 16-bit, 0–32767 for signed 16-bit, 0–255 for 8-bit, 0–1 for floating point). The cut points are chosen by rank, not by fixed grey values, so two scans with different brightness or contrast end up with a similar spread, and a handful of extreme voxels can no longer set the scale. The percentiles are computed over the whole volume, background included, and the reference scan is normalized too. The stored threshold goes through the same straight line, so the segmented shape stays the same as long as the threshold lies inside the window.
The reference-based modes work differently. Histogram Matching reshapes the above-threshold histogram of each scan to match the reference. Align Intensity Peaks shifts all values so that the scan's threshold lines up with the reference threshold. Tissue-aware Normalization rescales the soft-tissue and bone ranges separately.
Signs of poor parameters
- The high percentile cuts into the brightest anatomy. Percentiles count every voxel, air included, so when the specimen fills only a small part of the field of view a 98th percentile can land inside the specimen and flatten bone or other dense structures into uniform white.
- The low percentile is above the segmentation threshold. The threshold is then mapped to the minimum, it no longer separates specimen from background, and meshes or masks built from it will be wrong.
- A window of 0–100 %. That is plain min–max stretching, so the outliers still set the scale and nothing is gained.
- A very narrow window (for example 20–80 %). Large parts of real anatomy are clipped at both ends.
- The same percentiles on scans cropped very differently (one tight, one with lots of air). The same percentile then falls on different tissue in each scan, which brings back the differences the tool is meant to remove.
Good parameters
- A low percentile that falls in the background. After Background Offset Correction the background is already at zero, so the low cut only needs to stay inside that zero spike (often a very low value, close to 0 %); it must never reach tissue or the threshold.
- A high percentile just above the brightest structure you care about, so it clips only isolated hot voxels, streaks or metal. A few scattered clipped voxels are fine; a whole structure turning white is not.
- The defaults (2 % and 99.5 %) are a safe starting point, not a rule. When the specimen fills a small fraction of the field of view, the high value may need to be even closer to 100 % (for example 99.9 %); check with Shift on the max orb.
- One percentile pair for the whole cohort, on scans cropped in a similar way, so that the background fraction is comparable.
Finding the best values
- Run Background Offset Correction first, so the low end is already anchored at zero in every subject.
- Open a representative scan, ideally the one whose specimen fills the smallest part of the field of view, or the one with the brightest outliers.
- Drag the max orb of the intensity slider and hold Shift: voxels that would clip turn magenta. Move it down until the magenta just starts to touch structures you care about, then move it back up a little. The label on the orb shows the percentile of that grey value.
- Do the same with the min orb: voxels that would clip turn cyan. Cyan should cover air or background but not tissue, and the orb should stay below the threshold.
- Enter those two percentiles in Percentile Normalization. Click the percentage text to type an exact value.
- Repeat on one or two contrasting scans (largest specimen, darkest scan). If they suggest different values, use the most conservative pair (the lowest low and the highest high), so no scan loses anatomy.
- Turn on Normalize only current scan, run it, check the result, then run the batch.
How to check the result
- Look at the histogram of the new edit. The tissue peaks should sit at similar positions in every subject, with at most a thin spike at each end from clipped voxels. A tall spike at the top means a whole structure was clipped: raise the high percentile.
- Show two or three subjects with the same display window. They should look alike in brightness and contrast.
- Check that the threshold still separates specimen from background. A mesh preview should match the one from before normalization.
- To try other values, delete the histmatched edit first. Batch runs skip scans that are already normalized.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| reference | string|null | | Reference scan name. Required by the API for all methods (including percentile_normalize); reference-based methods use it for matching, while percentile_normalize still expects it in the request. |
| method | string | histogram_match | Normalization method: histogram_match, peak_align, tissue_aware, or percentile_normalize. |
| percentile_low | number | 2 | Lower percentile used by percentile_normalize. |
| percentile_high | number | 99.5 | Upper percentile used by percentile_normalize (default 99.5, raised from 98 because percentiles include air and 98 can clip bright anatomy on small specimens). |
| onlyCurrentScan | boolean | False | When true, normalize only selectedScan. |
| selectedScan | string|null | | Scan name to process when onlyCurrentScan is true. |
| flagFilter | string | off | Subject flag filter: off, exclude flagged scans, or only flagged scans. |
Algorithm / details
In batch mode (not onlyCurrentScan), skip linked children. histogram_match: match foreground voxel intensities to the reference foreground using skimage.exposure.match_histograms. peak_align: add (T_ref - T_scan) to all voxels and clip to dtype range. tissue_aware: detect soft-tissue and bone peaks in reference and scan foreground, linearly rescale each range separately. percentile_normalize: clip the full volume to [p_low, p_high] and linearly map to the dtype range, remapping threshold through the same transform; reference scan is included. All methods save histmatched edits, lossy copies, updated thresholds, and copied masks/landmarks.
v' = \mathrm{clip}(v + T_{\mathrm{ref}} - T_{\mathrm{scan}})
peak_align: translate intensities so the scan threshold/peak lines up with the reference.
v' = \frac{\mathrm{clip}(v, p_L, p_H) - p_L}{p_H - p_L}\,(M - m) + m
percentile_normalize (recommended): stretch the [p_L, p_H] window onto dtype [m, M]; defaults p_L = 2nd percentile, p_H = 99.5th (UI-adjustable).
flowchart TD
A["Choose method"] --> B{"percentile_normalize?"}
B -->|"yes"| C["Clip + rescale each scan"]
B -->|"no"| D["Load reference scan"]
D --> E{"method"}
E -->|"histogram_match"| F["Match foreground histograms"]
E -->|"peak_align"| G["Shift by threshold difference"]
E -->|"tissue_aware"| H["Rescale soft tissue and bone ranges"]
F --> I["Save histmatched edit"]
G --> I
H --> I
C --> I
Related: apply-threshold, n4-bias-correction, homogenize-background
N4 Bias Correction
Keywords: N4, bias field, intensity inhomogeneity, ANTs, MRI, batch
Summary
Corrects low-frequency MRI intensity inhomogeneity with ANTs N4 bias-field correction (Tustison et al., 2010) via ants.n4_bias_field_correction.
Considerations
Why use it
Removes the smooth brightness drift across an MRI (one side or the centre brighter than the rest), so the same tissue has the same intensity everywhere in the image.
Use it when
- MRI scans show smooth shading from coil sensitivity or field inhomogeneity.
- Before thresholding, segmentation or intensity-based registration of MRI.
Skip it when
- CT and micro-CT, which do not have this kind of bias field (beam hardening is a different artefact).
- Scans without visible shading; the correction then changes little but takes time.
Effect on later steps
- Thresholding and segmentation (Helps): one threshold works across the whole image.
- Intensity Normalization (Helps): histograms are sharper once the drift is gone, so peaks and percentiles are more reliable.
- Elastic registration (Helps): especially with Mean squares or Demons, which assume constant tissue intensity.
Why it works
N4 estimates a very smooth multiplicative field (a B-spline surface) that explains the slow brightness changes, and divides the image by it. Fine structure is too detailed for the smooth field to model, so it is preserved.
Signs of poor parameters
- A spline spacing that is too small: the field starts following anatomy and flattens real contrast between tissues.
- A shrink factor so large that the field is estimated from too few voxels on small scans.
Good parameters
- The defaults (shrink 4, 50 iterations per level, spline spacing 200 mm) for whole-head or whole-body MRI.
- A smaller spline spacing only for small fields of view where the shading changes faster than 200 mm.
Finding the best values
- Run on one scan with the defaults.
- Compare a tissue's brightness at opposite sides of the image before and after.
- If shading remains, reduce the spline spacing in steps; stop if tissue contrast starts to flatten.
How to check the result
- Show the scan before and after under one display window: the shading should be gone and tissue contrast should look the same.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| filename | string|null | | Optional single scan name; when set, only that scan is corrected. |
| flagFilter | string | off | Subject flag filter for batch mode: off, exclude flagged scans, or only flagged scans. |
| parameters | object | {} | Nested N4 tuning options passed to ANTs. |
| parameters.shrink_factor | integer | 4 | Downsampling factor for bias-field estimation speed. |
| parameters.convergence_iters | integer | 50 | Iterations per resolution level in the N4 convergence schedule. |
| parameters.convergence_tol_exp | integer | 7 | Exponent for convergence tolerance: tol = 10^(-convergence_tol_exp). |
| parameters.spline_param | integer | 200 | B-spline control-point spacing parameter for the estimated bias field. |
Algorithm / details
1) Select eligible voxel scans (in batch mode when filename is unset, skip linked children); load the latest edit as float32. 2) Run N4 with defaults shrink_factor = 4, 50 iterations per level across 4 hierarchical levels, spline_distance = 200 mm, convergence tolerance = 1e-07 (overridable in the UI). 3) Clip the corrected volume to the original dtype range, save _n4 / bias-corrected edit plus lossy copy, propagate masks/landmarks, emit progress.
I_{\mathrm{corr}}(x) = I(x) / B(x)
I: observed intensity; B: smooth bias field from iterative B-spline N4 fitting; x: voxel. Defaults: shrink 4, 50×4 iterations, spline distance 200 mm, tol 1e-07.
flowchart TD
A["Select eligible scans"] --> B["Load latest edit as float32"]
B --> C["Run ANTs N4 bias correction"]
C --> D["Clip to original dtype"]
D --> E["Save n4corrected edit + lossy"]
E --> F["Copy masks and landmarks"]
Related: match-histogram, apply-threshold
Save Threshold Values
Keywords: threshold, metadata, manual, per-scan, UI values
Summary
Persists user-edited threshold values from the UI into each scan's metadata JSON without modifying voxel data. Useful after manual threshold review in the viewer when values should be saved for mesh extraction, cleanup, or downstream preprocessing steps.
Considerations
Why use it
Saves thresholds you adjusted by eye for several subjects at once, so meshes and downstream tools use them.
Use it when
- You reviewed thresholds subject by subject in the viewer and want to keep the values.
Effect on later steps
- Meshes, cleanup, background tools, landmark snapping (Caution): Intended, just be aware: all use the saved values.
Why it works
Each value is written into the subject's metadata. The voxel data does not change.
How to check the result
- Reopen a couple of subjects and confirm their meshes use the new thresholds.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| values | object | {} | Map of scan_name to objects containing a numeric threshold field to write into metadata. |
Algorithm / details
For each scan entry in values, load extracted/{scan_name}/{scan_name}.json, update the threshold field when present, and save the metadata file. Missing metadata files are reported as errors; successful updates are returned in updated_scans.
Related: apply-threshold, cleanup-mesh, remove-background
Metadata Utilities
Get Rotation Matrix
Keywords: rotation, orientation, metadata, rotated_by_matrix, identity matrix
Summary
Reads the 3×3 rotation matrix stored in scan metadata under rotated_by_matrix.rotation_matrix, or returns the identity matrix when metadata is missing or no rotation was recorded. Useful for frontend visualization, coordinate transforms, or applying/displaying prior orientation corrections without reloading the full volume.
Considerations
Why use it
Reports the rotation that was applied to a scan, so the viewer and coordinate tools can relate the rotated scan to the original orientation.
Use it when
- It runs automatically when views or exports need the original orientation.
Why it works
It reads the rotation stored in the subject's metadata, or reports no rotation (identity) when none was applied.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| scan_name | string | | Scan whose metadata JSON is queried for rotated_by_matrix.rotation_matrix. |
Algorithm / details
Open extracted/{scan_name}/{scan_name}.json. If rotated_by_matrix.rotation_matrix exists, return it. Otherwise return the 3×3 identity matrix. Missing metadata also yields identity with a success response.
flowchart TD
A["Read scan metadata JSON"] --> B{"rotation_matrix present?"}
B -->|"yes"| C["Return stored 3x3 matrix"]
B -->|"no"| D["Return identity matrix"]
Denoise & Restore
Batch Denoising
Denoise
Keywords: denoise, gaussian, median, non-local means, nlm, total variation, wavelet, butterworth, batch, nifti
Summary
Applies a chosen denoising algorithm to every eligible voxel-based scan in a project directory, saving a new edit tagged _denoised with full and lossy NIfTI copies plus propagated masks and landmarks. Use this when scan data is noisy from acquisition or reconstruction and you want consistent filtering across subjects before segmentation, meshing, or visualization. Skips faulty scans, preserved PLY meshes, scans that already have a latest _denoised edit, and scans with elastic registration. Supports Gaussian blur, median filtering, Non-Local Means (2.5D CPU parallel, 3D CPU, or 3D GPU via nlm3d), Total Variation (Chambolle or Bregman), wavelet shrinkage, and Butterworth frequency filtering.
Considerations
Why use it
Reduces random noise in every scan while trying to keep edges, so thresholds, segmentation and registration react to anatomy instead of noise.
Use it when
- Meshes look grainy or bubbly at any reasonable threshold, or thin structures break up because of noise.
- AI segmentation or edge detection picks up noise.
- Low-dose, fast or high-resolution scans with a poor signal-to-noise ratio.
Skip it when
- Scans are already clean; denoising then only risks losing fine detail.
- Your measurement depends on fine texture or exact grey values (for example trabecular texture or mineral density); every method changes them.
Effect on later steps
- Thresholds and meshes (Helps): cleaner surfaces at the same threshold, fewer specks to clean up.
- Elastic registration (Helps): the similarity metric follows anatomy rather than noise; mostly with Mean squares and Demons.
- AI segmentation (Helps): fewer false detections in noisy regions.
- Background Offset Correction (No difference): the background peak stays where it was; it only becomes narrower.
Why it works
Gaussian and median filters average each voxel with its neighbours (median is better at keeping edges and removing isolated hot voxels). Non-Local Means averages voxels whose surrounding patches look alike, even if they are far apart, which keeps edges and repeated texture. Total Variation favours piecewise-flat regions with sharp borders. Wavelet shrinkage removes small coefficients that are mostly noise. Butterworth filters frequencies: low-pass removes fine noise, high-pass removes smooth shading and keeps edges (and noise).
Signs of poor parameters
- Strength too high (Gaussian sigma, NLM h, TV weight): thin structures and small gaps blur away, surfaces look melted or plastic.
- Butterworth high-pass when the goal is noise removal: it keeps the noise and removes the smooth signal. Use low-pass to denoise.
- Different settings for different subjects of one study: the subjects are no longer processed the same way.
Good parameters
- The weakest setting that makes the noise acceptable, checked in the preview on the thinnest structure you care about.
- Non-Local Means for most micro-CT and MRI data; median for salt-and-pepper (isolated bright or dark voxels); a small Gaussian (sigma under 1 voxel) only for light smoothing.
- NLM h close to the noise level of the normalized image; patch 5–7 voxels; a larger search window only if it clearly helps (it is much slower).
Finding the best values
- Open Denoise preview on a region with both noise and the finest structure you care about.
- Start weak and increase the strength until the noise in a flat region is acceptable.
- Stop before the fine structure's edges soften; back off one step.
- Check the same settings on a second, noisier subject before running the batch.
How to check the result
- Compare meshes before and after at the same threshold: fewer specks, same thin structures.
- Look at a line profile or slice across a thin edge: it should stay sharp.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root containing extracted/ scan folders. |
| algorithm | string | Gaussian | Denoising method: Gaussian, Median, NonLocalMeans, TotalVariationChambolle, TotalVariationBregman, Wavelet, or Butterworth. |
| parameters | object | {} | Algorithm-specific settings (see individual keys below). |
| parameters.sigma | float | 1.0 (Gaussian) / 0.1 (Wavelet) | Gaussian blur standard deviation, or wavelet noise estimate when algorithm is Wavelet. |
| parameters.truncate | float | 4.0 | Gaussian filter truncate radius (Gaussian only). |
| parameters.h | float | 0.01 | NLM filter strength h on normalized [0,1] data (NonLocalMeans only). |
| parameters.patch_size | int | 7 | NLM patch diameter in voxels (NonLocalMeans only). template_size is still accepted from older callers. |
| parameters.patch_distance | int | 11 | NLM search distance in voxels (NonLocalMeans only). search_size is still accepted from older callers. |
| parameters.mode | string | 2.5D | NLM execution mode: 2.5D (multi-axis slab average), 3D_CPU (skimage), or 3D_GPU (PyTorch nlm3d). |
| parameters.weight | float | 0.1 (Chambolle) / 5.0 (Bregman) | TV denoising weight (TotalVariationChambolle or TotalVariationBregman). |
| parameters.eps | float | 0.0002 (Chambolle) / 0.001 (Bregman) | TV stopping tolerance (TotalVariationChambolle or TotalVariationBregman). |
| parameters.max_num_iter | int | 200 (Chambolle) / 100 (Bregman) | Maximum TV iterations (TotalVariationChambolle or TotalVariationBregman). |
| parameters.wavelet | string | db1 | Wavelet basis name (Wavelet only). |
| parameters.level | int | 1 | Wavelet decomposition level (Wavelet only). |
| parameters.cutoff | float | 0.005 | Butterworth cutoff frequency ratio (Butterworth only). |
| parameters.order | int | 2 | Butterworth filter order (Butterworth only). |
| parameters.high_pass | bool | false | If true, apply high-pass Butterworth (keeps edges and noise, removes smooth signal); false (default) is low-pass, which denoises (Butterworth only). |
| parameters.squared_butterworth | bool | true | Use squared Butterworth response (Butterworth only). |
| denoiseAutoMode | object | {} | Per-parameter auto-mode flags sent from the UI (currently logged but not applied server-side). |
| onlyCurrentScan | bool | false | When true, restrict processing to selectedScan. |
| selectedScan | string|null | null | Scan folder name to process when onlyCurrentScan is true. |
| flagFilter | string | off | Subject flag filter: off | exclude | only. |
Algorithm / details
1) Enumerate extracted scan folders, excluding faulty metadata and preserved meshes. 2) Optionally filter to one scan, apply flag filter, skip linked children in batch mode, and skip scans whose latest edit is already _denoised or that have elastic registration. 3) For each remaining scan, load the latest NIfTI edit (or original). 4) Normalize intensities to [0,1] when the chosen backend requires it, then rescale the result back to the original dtype range after denoising. 5) Apply the user-selected denoising method — Aurora dispatches to one of the backends below (library filters, or Aurora’s own 3D GPU Non-Local Means when mode is 3D_GPU). 6) NonLocalMeans orchestration: with split=True, large volumes are tiled; mode 2.5D runs skimage NLM along three axes and averages; mode 3D_CPU runs skimage NLM on cubic chunks in parallel; mode 3D_GPU calls Aurora’s nlm3d CUDA implementation with an OOM tiling fallback. 7) Save {scan}_edit_{n}_denoised.nii.gz plus lossy copy, copy masks/landmarks, and broadcast WebSocket progress.
Gaussian — SciPy — scipy.ndimage.gaussian_filter: Isotropic Gaussian smoothing (SciPy). (https://docs.scipy.org/doc/scipy/reference/generated/scipy.ndimage.gaussian_filter.html)
Median — scikit-image — skimage.filters.median: Rank-order median filter (scikit-image). (https://scikit-image.org/docs/stable/api/skimage.filters.html#skimage.filters.median)
NLM (2.5D / 3D CPU) — scikit-image — skimage.restoration.denoise_nl_means: scikit-image Non-Local Means. Aurora tiles/parallelizes; the filter itself is the library call. (https://scikit-image.org/docs/stable/api/skimage.restoration.html#skimage.restoration.denoise_nl_means)
NLM (3D GPU) — Aurora (in-house) — GPUTorchNLMDenoise.nlm3d: Aurora’s own PyTorch/CUDA 3D Non-Local Means (nlm3d), not a third-party package API. [docs:gpu-nlm3d]
TV Chambolle — scikit-image — skimage.restoration.denoise_tv_chambolle: Total-variation denoising (Chambolle). (https://scikit-image.org/docs/stable/api/skimage.restoration.html#skimage.restoration.denoise_tv_chambolle)
TV Bregman — scikit-image — skimage.restoration.denoise_tv_bregman: Total-variation denoising (split-Bregman). (https://scikit-image.org/docs/stable/api/skimage.restoration.html#skimage.restoration.denoise_tv_bregman)
Wavelet — scikit-image — skimage.restoration.denoise_wavelet: BayesShrink soft-threshold wavelet denoising. (https://scikit-image.org/docs/stable/api/skimage.restoration.html#skimage.restoration.denoise_wavelet)
Butterworth — scikit-image — skimage.filters.butterworth: Frequency-domain Butterworth filter. (https://scikit-image.org/docs/stable/api/skimage.filters.html#skimage.filters.butterworth)
flowchart TD
A["POST directory + algorithm"] --> B["Filter eligible scans"]
B --> C{"onlyCurrentScan?"}
C -->|"yes"| D["Single scan"]
C -->|"no"| E["All scans"]
D --> F["Load latest NIfTI"]
E --> F
F --> G{"algorithm"}
G -->|"Gaussian/Median/TV/Wavelet/Butterworth"| H["skimage/scipy filter"]
G -->|"NonLocalMeans"| I{"mode"}
I -->|"2.5D"| J["Parallel slab NLM + axis average"]
I -->|"3D_CPU"| K["skimage denoise_nl_means"]
I -->|"3D_GPU"| L["nlm3d CUDA + OOM fallback"]
H --> M["Save _denoised edit + lossy + masks"]
J --> M
K --> M
L --> M
Related: preview-denoise, gpu-nlm3d, restore-all-scans, n4-bias-correction
Denoise Preview
Denoise preview
Keywords: denoise, preview, patch, slice, interactive, cache
Summary
Interactive preview endpoint that denoises a 100×100×100 voxel patch centered at user-specified coordinates and returns normalized center Z-slice pixels for raw and denoised data. Use this to tune denoising parameters in the Aurora UI before running batch Denoise All Scans. Raw patches are cached (max one entry) by scan name and coordinates so parameter sweeps skip repeated disk I/O; denoising is recomputed on every request so slider changes appear immediately.
Considerations
Why use it
Shows the effect of a denoising method on a small patch of the scan instantly, so you can tune parameters before spending time on the whole cohort.
Use it when
- Always before running Denoise on a cohort, and whenever you switch method.
Why it works
A 100-voxel cube around the chosen point is denoised with the current settings and the middle slice is shown next to the raw slice. The raw patch is kept in memory, so changing settings updates quickly.
Signs of poor parameters
- Tuning on a patch of empty background or flat tissue only: you cannot see what happens to edges and fine structure.
- Judging contrast between the two images: each slice is stretched to its own display range, so compare sharpness and noise, not brightness.
Good parameters
- A patch that contains noise, a sharp edge and the finest structure you need to keep.
How to check the result
- Check two or three different regions before deciding; some methods behave differently in dense and thin areas.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root containing extracted/ scan folders. |
| scan_name | string | required | Scan folder name to preview. |
| algorithm | string | Gaussian | Same algorithm names as DenoiseAllScansView. |
| parameters | object | {} | Algorithm-specific settings for the preview patch. |
| parameters.sigma | float | 1.0 (Gaussian) / 0.1 (Wavelet) | Gaussian sigma or wavelet noise estimate. |
| parameters.truncate | float | 4.0 | Gaussian truncate (Gaussian only). |
| parameters.h | float | 0.1 | NLM filter strength (NonLocalMeans only). |
| parameters.patch_size | int | 7 | NLM template patch size in voxels (NonLocalMeans only; maps to template_size internally). |
| parameters.patch_distance | int | 11 | NLM search distance in voxels (NonLocalMeans only; maps to search_size internally). |
| parameters.mode | string | 2.5D | NLM mode passed to denoise_nl_means_module with split=False. |
| parameters.weight | float | 0.1 (Chambolle) / 5.0 (Bregman) | TV denoising weight. |
| parameters.eps | float | 0.0002 (Chambolle) / 0.001 (Bregman) | TV stopping tolerance. |
| parameters.max_num_iter | int | 200 (Chambolle) / 100 (Bregman) | Maximum TV iterations. |
| parameters.wavelet | string | db1 | Wavelet basis (Wavelet only). |
| parameters.level | int | 1 | Wavelet level (Wavelet only). |
| parameters.cutoff | float | 0.005 | Butterworth cutoff ratio (Butterworth only). |
| parameters.order | int | 2 | Butterworth order (Butterworth only). |
| parameters.high_pass | bool | false | High-pass vs low-pass Butterworth (Butterworth only). |
| parameters.squared_butterworth | bool | true | Squared Butterworth response (Butterworth only). |
| denoiseAutoMode | object | {} | Per-parameter auto-mode flags from the UI (received but not applied server-side). |
| x | number | 0 | Patch center X coordinate in voxel space. |
| y | number | 0 | Patch center Y coordinate in voxel space. |
| z | number | 0 | Patch center Z coordinate in voxel space. |
Algorithm / details
1) Build cache key from scan_name and integer (x,y,z). 2) On cache miss, load latest edit or original NIfTI and extract a 100³ patch (zero-padded at boundaries). 3) On cache hit, reuse the cached raw patch. 4) Delegate to DenoiseAllScansView denoise helpers with preview-specific parameter names (patch_size/patch_distance for NLM). 5) Extract the center Z slice from raw and denoised volumes, min-max normalize to uint8, rotate 90° CCW for display orientation, flatten to lists, and return slice_shape plus from_cache flag.
flowchart TD
A["POST coords + algorithm"] --> B{"Cache hit?"}
B -->|"no"| C["Load NIfTI + extract 100³ patch"]
B -->|"yes"| D["Use cached raw patch"]
C --> E["Store raw patch in cache"]
D --> F["Apply denoise algorithm"]
E --> F
F --> G["Center Z slice raw + denoised"]
G --> H["Normalize + rot90 + flatten"]
H --> I["Return center_slice arrays"]
Related: denoise-all-scans, gpu-nlm3d
Restoration
Restore
Keywords: restore, clahe, contrast, histogram equalization, mask, batch, nifti, weld mesh, preserved mesh
Summary
Restore tooltip hosts voxel contrast restoration (CLAHE / N4) and, when the project has preserved PLY meshes, Weld mesh. CLAHE/N4 apply contrast or bias correction to eligible voxel scans and save _restored / _n4corrected edits. Weld mesh is a separate batch path (BatchWeldMeshView) that proximity-welds near-touching vertices across disconnected mesh components and writes a _welded PLY edit with mesh_weld_settings provenance. The outer Restore control stays enabled for mesh-only projects so Weld mesh remains reachable; CLAHE and N4 stay disabled without voxel volumes. CLAHE is meant for scans where tissue structure is present but underexposed, or to bring out faint low-intensity anatomy. Batch CLAHE skips preserved PLY meshes, faulty scans and scans whose latest edit is already restored.
Considerations
Why use it
Brings out faint structure. CLAHE raises local contrast so dim, underexposed tissue becomes visible; N4 removes smooth MRI shading; Weld mesh joins fragmented preserved meshes.
Use it when
- Tissue structure is present but underexposed, or faint low-intensity features need to be brought out (CLAHE).
- You need to see or segment faint soft tissue next to much brighter structures.
- MRI with smooth shading (N4), or fragmented PLY meshes (Weld mesh).
Skip it when
- You will compare or measure grey values between subjects or regions: CLAHE changes contrast differently in each region, so values are no longer comparable. A real risk for intensity measurements.
- Intensity-based registration with Mean squares or Demons follows CLAHE: local contrast changes differ between subjects. A real risk with those metrics; much smaller with CC or Mattes.
Effect on later steps
- Visual inspection and manual segmentation (Helps): faint structures are easier to see and trace.
- AI segmentation (Helps): when the structure of interest is faint; the model sees more contrast.
- Intensity Normalization and intensity measurements (Caution): Real risk if you compare grey values between subjects or regions; worth it when you only need to see or segment faint structures. CLAHE reshapes each scan's histogram differently, which defeats comparisons of absolute intensity.
Why it works
CLAHE splits the volume into regions and equalizes each region's histogram, limiting how much contrast may be boosted (the clip limit) to avoid amplifying noise. The result is rescaled to the original range. With a mask, only the masked region is changed.
Signs of poor parameters
- A high clip limit: noise is amplified along with the faint structure, and flat regions look grainy.
- Very small regions (kernel size): contrast becomes patchy, with visible block edges.
Good parameters
- The default clip limit (0.01) and automatic region size, increased slowly only if the structure is still too faint.
- Restrict to a mask when only one structure needs boosting.
Finding the best values
- Run on the current scan with defaults.
- If the target is still faint, raise the clip limit a little; stop when flat areas start to look grainy.
- Denoise before CLAHE if noise becomes visible.
How to check the result
- Compare before and after in the same slices: faint structures should be clearer and flat regions should not turn grainy.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root containing extracted/ scan folders. |
| algorithm | string | CLAHE | UI selection: CLAHE, BiasCorrection (N4), or WeldMesh (preserved meshes only; routed to /batch-weld-mesh/). |
| parameters | object | {} | Algorithm-specific settings (CLAHE/N4 params, or WeldMesh distance_factor). |
| parameters.kernel_size | int | 0 | CLAHE local window size in voxels per axis. 0 means auto (skimage uses 1/8 of each dimension). |
| parameters.clip_limit | float | 0.01 | CLAHE clip limit controlling contrast amplification (higher = stronger). |
| parameters.nbins | int | 256 | Number of histogram bins for CLAHE. |
| onlyCurrentScan | bool | false | When true, restrict processing to selectedScan. |
| selectedScan | string|null | null | Scan folder name when onlyCurrentScan is true. |
| prefer_mask | bool | false | When true with CLAHE, apply restoration only inside the segmentation mask; outside voxels keep original intensities. |
| mask_label | int | 1 | Mask voxel value treated as foreground when prefer_mask is true. |
| flagFilter | string | off | Subject flag filter: off | exclude | only. |
Algorithm / details
Voxel CLAHE path: 1) Enumerate eligible voxel scans (faulty, meshes, flags, skip linked children in batch mode, skip existing _restored). 2) Load latest NIfTI per scan and normalize to [0,1]. 3) Run skimage exposure.equalize_adapthist with kernel_size (auto when 0), clip_limit, and nbins. 4) Rescale restored values back to the original intensity range and dtype. 5) If prefer_mask, load the mask paired with the source edit and blend: restored inside mask_label voxels, original elsewhere. 6) Save {scan}_edit_{n}_restored.nii.gz, lossy copy, propagate masks and landmarks, broadcast progress. Weld mesh path (UI algorithm WeldMesh): POST /batch-weld-mesh/ — see batch-weld-mesh.
\mathrm{clip\_limit} = c \cdot \overline{h}
CLAHE: c is the user clip_limit multiplier (default parameters.clip_limit = 0.01); h̄ is the mean histogram bin count in a tile; peaks above c·h̄ are redistributed before CDF mapping.
flowchart TD
A["POST directory + CLAHE params"] --> B["Filter eligible scans"]
B --> C["Load latest NIfTI"]
C --> D["Normalize to 0-1"]
D --> E["skimage equalize_adapthist"]
E --> F{"prefer_mask?"}
F -->|"yes"| G["Blend with original outside mask_label"]
F -->|"no"| H["Use full restored volume"]
G --> I["Save _restored edit + lossy + masks"]
H --> I
Related: denoise-all-scans, batch-weld-mesh
GPU Non-Local Means
GPU 3D Non-Local Means (nlm3d)
Keywords: nlm, non-local means, gpu, cuda, pytorch, denoise, 3d, tiling, oom
Summary
Aurora’s in-house PyTorch/CUDA 3D Non-Local Means (GPUTorchNLMDenoise.nlm3d → apply_nonlocal_means_3d_with_fallback), used when Denoise runs NonLocalMeans with mode 3D_GPU. Accepts a normalized [H,W,D] float tensor on GPU, compares patches in a search neighborhood with skimage-style noise compensation, and on CUDA OOM falls back to context-padded tiled processing. This is not a scikit-image call — the CPU/2.5D NLM paths use skimage.restoration.denoise_nl_means instead.
Considerations
Why use it
Runs full 3D Non-Local Means on the graphics card, which is much faster than the CPU path on large volumes and uses true 3D patches.
Use it when
- You chose Non-Local Means in Denoise, have an NVIDIA GPU, and want the 3D variant on large scans.
Skip it when
- No CUDA GPU is available; use the 2.5D or 3D CPU modes.
Why it works
Each voxel is replaced by a weighted average of voxels in its search window, weighted by how similar their surrounding patches are. If the GPU runs out of memory, the volume is processed in overlapping tiles automatically, which gives the same result more slowly.
Signs of poor parameters
- A large search window on a big volume: run time grows with the cube of the window size.
Good parameters
- The same h and patch size you tuned in Denoise preview; keep the search window modest.
How to check the result
- Compare a slice from the GPU result with the CPU 3D result on a small scan; they should look alike.
Inputs / options
| Name | Type | Default | Description |
|---|
| X | torch.Tensor [H,W,D] | required | Input 3D volume (typically float32, normalized 0–1, on CUDA). |
| kernel_size | int | 3 | Search neighborhood diameter: number of offset voxels along each axis (odd integer; denoiseTools passes 2*search_size+1 for split GPU path or search_size directly in non-split path). |
| std | float | 1.0 | Filter strength h (denoiseTools maps parameters.h here). |
| kernel_size_mean | int | 3 | Mean-filter patch diameter used before distance computation (denoiseTools maps parameters.template_size here). |
| sub_filter_size | int | 256 | Approximate number of neighbor offsets processed per streaming iteration in the memory-efficient path. |
| debug | bool | false | Enable verbose debug logging during NLM processing. |
Algorithm / details
nlm3d is an alias for apply_nonlocal_means_3d_with_fallback. Primary path: apply_nonlocal_means_3d_mem_efficient streams neighbor offsets through non_local_means_loop_index, comparing mean-filtered patches and weighting original unsmoothed neighbors by exp(-distance²/(2h²)) with skimage-style noise variance compensation. On RuntimeError containing 'out of memory', copies input to CPU, clears CUDA cache, picks tile size 96–160 based on free VRAM, and runs apply_nonlocal_means_3d_tiled: context-padded tiles are processed on GPU via _process_tile_streaming with per-offset streaming, then center results are stitched into the full volume.
w(p,q) = \exp\!\left(-\frac{\|P(p)-P(q)\|^2}{2h^2}\right)
w: patch-similarity weight; P(·): local 3D patch; h: filter strength.
\hat{I}(p) = \frac{\sum_q w(p,q)\, I(q)}{\sum_q w(p,q)}
Denoised value at p is the normalized weighted sum over search-window voxels q.
flowchart TD
A["nlm3d tensor on CUDA"] --> B["apply_nonlocal_means_3d_mem_efficient"]
B --> C{"OOM?"}
C -->|"no"| D["Return denoised volume"]
C -->|"yes"| E["Move to CPU + empty_cache"]
E --> F["Pick tile size from free VRAM"]
F --> G["apply_nonlocal_means_3d_tiled"]
G --> H["Stream offsets per tile"]
H --> D
Related: denoise-all-scans, preview-denoise
Masks & Labels
Mask I/O
Save Mask
Keywords: mask, save, brush, nifti, full resolution, lossy, binary
Summary
Persists a painted or edited voxel mask from the Aurora canvas to disk as a NIfTI sidecar ({edit}.nii.mask.gz). Accepts either a compact binary upload or a legacy JSON uint8 array. Saves at the resolution you are currently editing (full or lossy) and automatically generates a complementary-resolution mask by nearest-neighbor zoom so both preview and export resolutions stay in sync.
Considerations
Why use it
Saves the mask you painted or edited, at both the preview and the full resolution, so painting, AI segmentation, transfers and exports all see the same labels.
Use it when
- It runs whenever you save brush work or a tool writes a mask.
Why it works
The mask is saved at the resolution you are editing; the other resolution is produced by nearest-neighbour resizing, which keeps label values exact.
Signs of poor parameters
- Painting fine detail on the preview resolution and expecting it to be exact at full resolution: the full-resolution copy is an upscaled version and looks blocky at edges.
Good parameters
- Paint fine boundaries in full-resolution mode; use the preview for quick rough labels.
How to check the result
- Reload the subject at full resolution and check the edges of the label where precision matters.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory for the project or subject collection. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas masks. |
| edit | str | null | Edit stem without .nii.gz (e.g. scan_edit_1). When omitted, defaults to the raw scan / scan_lossy pair. |
| fullResolutionState | bool | str | false | True when the canvas is editing the full-resolution volume; false for the lossy preview volume. Multipart string values true/1/yes/on are accepted. |
| maskData | file | list[int] | required | Flat uint8 voxel array in frontend order (depth, height, width with Y/Z flipped). Prefer multipart file upload; JSON integer list is supported for legacy clients. |
Algorithm / details
1) Resolve source/target edit names from fullResolutionState and edit stem. 2) Load scan metadata to read resolution_factor from lossy_compression. 3) Reshape flat mask bytes to NIfTI (width, height, depth) with frontend axis flips. 4) Save source-resolution mask as {source_edit}.nii.mask.gz. 5) If the complementary image exists, scale the mask with scipy.ndimage.zoom (order=0) to that shape and save the paired mask.
z_i = t_i / s_i
z_i: per-axis zoom factor; t_i: target shape; s_i: source mask shape; nearest-neighbor zoom (order=0) follows.
flowchart TD
A["Flat maskData"] --> B{"fullResolutionState?"}
B -->|"true"| C["Save full-res mask"]
B -->|"false"| D["Save lossy mask"]
C --> E["Zoom to lossy shape"]
D --> F["Zoom to full shape"]
E --> G["Write .nii.mask.gz pair"]
F --> G
Related: load-mask, upload-mask, apply-mask-to-image
Load Mask
Keywords: mask, load, brush, binary, canvas
Summary
Loads a saved mask from disk back into the Aurora canvas. Returns raw uint8 bytes (application/octet-stream) instead of a JSON array for performance on large volumes. Response headers carry the mask filename and NIfTI dimensions so the frontend can reconstruct the flat brush buffer.
Considerations
Why use it
Loads a saved mask back into the painting canvas so you can review or continue editing it.
Use it when
- It runs automatically when you open masks on a subject.
How to check the result
- If a mask seems shifted, check you are viewing the edit the mask was saved with; masks belong to a specific edit.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas masks. |
| edit | str | null | Edit stem without .nii.gz. When omitted, uses filename (full) or filename_lossy (lossy). |
| fullResolutionState | bool | false | True to load the full-resolution mask; false for the lossy mask. |
Algorithm / details
1) Derive mask_edit from edit and fullResolutionState (insert or strip _lossy as needed). 2) Temporarily rename .nii.mask.gz to .nii.gz for nibabel. 3) Load NIfTI, reverse frontend axis flips (Y/Z reversed, depth-major flatten), round to uint8. 4) Return raw bytes with X-Mask-Name and X-Mask-Dimensions headers. 5) Restore the custom extension.
flowchart TD
A["Request edit + resolution"] --> B["Resolve .nii.mask.gz path"]
B --> C["Temp rename for nibabel"]
C --> D["Load + axis invert"]
D --> E["HttpResponse octet-stream"]
E --> F["Restore extension"]
Related: save-mask, download-mask
Upload Mask
Keywords: mask, upload, nifti, import, external
Summary
Imports an external NIfTI mask file (.nii or .nii.gz) into a subject folder. Always stores the uploaded volume as the full-resolution mask and automatically downscales a complementary lossy mask when the lossy image exists, keeping both resolutions aligned for canvas editing.
Considerations
Why use it
Imports a segmentation made in other software (for example 3D Slicer or ITK-SNAP) so it can be used with Aurora's tools.
Use it when
- You already have manual or external segmentations for some subjects.
Why it works
The file is stored as the full-resolution mask of the edit you chose, and a preview-resolution copy is made by nearest-neighbour resizing.
Signs of poor parameters
- A mask drawn on a different edit (for example before a crop or rotation): it no longer lines up with the image.
Good parameters
- A mask exported from the same edit, with the same dimensions and orientation as Aurora's full-resolution file (download that file first if needed).
How to check the result
- Overlay the uploaded mask on slices near the specimen edges to confirm it lines up.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas masks. |
| edit | str | required | Lossy edit name (e.g. scan_lossy.nii.gz). Defaults to {filename}_lossy.nii.gz when empty or null. |
| maskFile | file | required | Uploaded NIfTI mask; must end with .nii or .nii.gz. |
Algorithm / details
1) Validate extension and derive full_edit / lossy_edit from edit. 2) Read resolution_factor from subject metadata. 3) Save uploaded NIfTI as {full_edit}.nii.mask.gz. 4) If {lossy_edit}.nii.gz exists, nearest-neighbor zoom the mask to lossy dimensions and save {lossy_edit}.nii.mask.gz.
M_{\mathrm{lossy}} = \mathrm{zoom}(M,\, [L_i/F_i],\, order{=}0)
M: full-resolution mask; L_i/F_i: lossy vs full shape ratio per axis; order=0: nearest-neighbor label preservation.
flowchart TD
A["Uploaded .nii.gz"] --> B["Save full-res mask"]
B --> C{"Lossy image exists?"}
C -->|"yes"| D["Zoom mask to lossy shape"]
C -->|"no"| E["Done"]
D --> E
Related: save-mask, download-mask, delete-mask
Download Mask
Keywords: mask, download, export, nifti, full resolution
Summary
Downloads the full-resolution mask file for a given edit as an attachment. Strips _lossy from the edit name so users always receive the native full-res sidecar regardless of which resolution they were viewing.
Considerations
Why use it
Downloads a subject's full-resolution mask as a NIfTI file for use in other software or for archiving.
Use it when
- You want to analyse or edit a segmentation elsewhere.
Why it works
You always get the full-resolution mask, whichever resolution you were viewing.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas masks. |
| edit | str | required | Edit name (may include .nii.gz and/or _lossy). Defaults to {filename}.nii.gz when empty or null. |
Algorithm / details
1) Strip .nii.gz and _lossy to get full_edit. 2) Locate {full_edit}.nii.mask.gz under extracted/{filename} or atlas/. 3) Temporarily rename to standard .nii.gz extension. 4) Stream file via FileResponse with Content-Disposition attachment header. 5) Restore custom extension.
flowchart TD
A["edit param"] --> B["Derive full_edit"]
B --> C["Locate .nii.mask.gz"]
C --> D["Temp rename + FileResponse"]
D --> E["Restore extension"]
Related: load-mask, upload-mask
Delete Mask
Keywords: mask, delete, remove, full resolution, lossy
Summary
Removes both full-resolution and lossy mask sidecars for a given edit. Use this when clearing brush data before re-labeling or when discarding an obsolete mask without deleting the underlying image edits.
Considerations
Why use it
Removes a mask (both resolutions) from an edit without touching the image, so you can start labelling again.
Use it when
- A mask is wrong or obsolete and you want a clean start.
Skip it when
- You only want to remove one label; erase that label with the brush instead.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas masks. |
| edit | str | required | Edit name (may include .nii.gz and/or _lossy). Defaults to {filename}.nii.gz when empty or null. |
Algorithm / details
1) Derive full_edit and lossy_edit from edit. 2) Build paths for {full_edit}.nii.mask.gz and {lossy_edit}.nii.mask.gz. 3) Delete each file that exists. 4) Return 404 if neither file was found.
flowchart TD
A["edit param"] --> B["full + lossy mask paths"]
B --> C["Delete existing files"]
C --> D{"Any deleted?"}
D -->|"yes"| E["200 OK"]
D -->|"no"| F["404"]
Related: save-mask, copy-mask-to-reference
Copy Mask to Reference
Keywords: mask, copy, reference, transfer, batch alignment
Summary
Copies a saved mask from one subject scan to another (reference) scan in the same project. Copies both full-resolution and lossy sidecars when present, overwriting destination files atomically via a temp file. Use after rigid alignment when a mask drawn on one subject should seed labeling on a homologous reference.
Considerations
Why use it
Copies a mask from one subject to the reference, for example to seed labelling on the reference with work done on another subject.
Use it when
- The two subjects are already aligned so that the mask fits both.
Skip it when
- The subjects are not aligned voxel to voxel; use Transfer masks after elastic registration instead.
Why it works
The mask files are copied as they are; no warping is applied.
Signs of poor parameters
- Copying between subjects with different shapes or poses: the mask will not fit the destination anatomy.
How to check the result
- Overlay the copied mask on the reference and check boundaries before building on it.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory containing extracted/ subjects. |
| selected_scan | str | required | Source subject folder name. |
| selected_edit_mask | str | required | Source mask filename (typically the lossy mask name passed from the frontend). |
| reference_scan | str | required | Destination subject folder name; must differ from selected_scan. |
| reference_edit_mask | str | required | Destination mask filename stem (lossy form from frontend). |
Algorithm / details
1) Reject if selected_scan equals reference_scan. 2) Locate full and lossy source masks under extracted/{selected_scan}/. 3) For each found mask, copy to extracted/{reference_scan}/ with the reference edit name (strip _lossy for full, keep for lossy). 4) Use shutil.copy2 to a .tmp file, verify size, then atomic os.replace. 5) Report copied and overwritten filenames.
flowchart TD
A["Source scan + mask"] --> B["Find full + lossy masks"]
B --> C["Map to reference edit names"]
C --> D["Atomic copy via .tmp"]
D --> E["Return copied/overwritten list"]
Related: save-mask, apply-mask-to-image
List Mask Folders
Keywords: mask, folders, list, batch, organization
Summary
Returns the names of existing mask output folders under {directory}/masks/. Use this to populate UI dropdowns before applying or batch-applying masks, or to confirm whether a named mask collection already exists.
Considerations
Why use it
Lists the masked datasets already created in this project, so you can add to one or avoid overwriting it.
Use it when
- It runs automatically when you choose a target folder in Apply masks.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory whose masks/ subfolder will be listed. |
Algorithm / details
1) Build path {directory}/masks/. 2) If the directory does not exist, return an empty list with count 0. 3) Otherwise list subdirectory names, sort alphabetically, and return mask_folders and count.
flowchart TD
A["directory"] --> B["masks/ exists?"]
B -->|"no"| C["Empty list"]
B -->|"yes"| D["List + sort subdirs"]
D --> E["Return mask_folders"]
Related: apply-mask-to-image, batch-apply-mask-to-scans
Apply Mask
Apply masks (single)
Keywords: mask, apply, zero, crop, mask folder, edit_0
Summary
Applies a saved full-resolution mask to its corresponding image edit and writes a new masked dataset into masks/{mask_folder}/. Non-masked voxels are zeroed, the result is saved as edit_0 (preserving any edit suffix), a lossy preview is generated, metadata is copied without lossy_compression, and elastic transforms from the next edit are renumbered to edit_1 when present.
Considerations
Why use it
Creates a copy of one subject in which everything outside its mask is set to zero, as a new masked dataset. This lets you register, view or export one structure on its own.
Use it when
- You want to analyse a single structure (for example one bone) without surrounding anatomy.
Why it works
Voxels where the mask is zero are set to zero, and the result is saved in masks/<folder>/ as a separate dataset, with any matching elastic transforms copied along.
How to check the result
- Open the masked dataset and check the structure is complete and nothing outside it remains.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas data. |
| edit | str | required | Source edit being masked (e.g. scan_lossy.nii.gz). Defaults to {filename}_lossy.nii.gz when empty or null. |
| mask_folder | str | required | Target folder name under masks/ where the masked copy will be stored. |
| mask_source | object|null | null | Optional {filename, edit} to read the mask from another scan (linked replay). When null, use this scan’s own mask. |
| create_new | bool | false | When true, logs initialization of a new mask folder (directory structure is always created as needed). |
Algorithm / details
1) Create masks/{mask_folder}/extracted/{filename}/ (or atlas/). 2) Copy project_settings.json once. 3) If the source edit has a following elastic edit, copy and rename elastic fwd/inv fields to edit_1. 4) Write an empty top-level {filename}.nii.gz placeholder. 5) Copy metadata minus lossy_compression. 6) Resolve the mask via mask_source when provided, else this scan’s mask; load full-res mask and image; zero voxels where mask == 0. 7) Save {filename}_edit_0{suffix}.nii.gz plus empty {filename}.nii.gz and {filename}_lossy.nii.gz placeholders. 8) Generate lossy edit_0 via RegistrationTools.save_as_lossy_nifti (fallback: scipy zoom).
I' = I \cdot \mathbf{1}[M > 0]
I: image; M: mask; voxels with M≤0 are zeroed.
flowchart TD
A["Load mask + image"] --> B["binary_mask = mask > 0"]
B --> C["Zero outside mask"]
C --> D["Save edit_0 full + lossy"]
D --> E["Copy metadata + elastic to mask folder"]
Related: batch-apply-mask-to-scans, list-mask-folders, save-mask, load-mask
Apply masks
Keywords: mask, batch, label, apply, dilation, flag filter, progress
Summary
Applies a single label value from each subject's latest full-resolution mask across all eligible scans in extracted/ (and atlas when present). Subjects without the label or without a mask are skipped. Optionally dilates the label region before zeroing everything outside it. Writes results into masks/{mask_folder}/ and broadcasts WebSocket progress updates.
Considerations
Why use it
Creates a masked copy of every subject that keeps only one label (for example one bone) and zeroes the rest. It turns a whole-specimen project into a project about one structure, without changing the original.
Use it when
- You want to register, atlas or measure a single structure on its own.
- Neighbouring structures distort registration of the one you care about.
Skip it when
- Subjects do not all have reliable masks for that label; those without the label are skipped and missing from the new dataset.
Effect on later steps
- Registration and atlas on the masked dataset (Helps): only the chosen structure drives the alignment.
Why it works
In each subject's latest full-resolution mask, the chosen label is selected (optionally grown by a few voxels), and the image is multiplied by it. Results go to masks/<folder>/ as a separate project-like dataset.
Signs of poor parameters
- No dilation on a label that is drawn tightly: the structure's outer surface gets cut, which biases surfaces and measurements.
- Large dilation: neighbouring structures leak back in.
Good parameters
- A small dilation (1–3 voxels) so the whole surface is included with a thin margin of background.
How to check the result
- Open a few masked subjects: the structure should be complete with a small background margin and no neighbours.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory containing extracted/. |
| label | int | required | Mask label value (0–255) to isolate and apply. |
| mask_folder | str | required | Target folder name under masks/. |
| create_new | bool | false | When true, explicitly creates the masks/{mask_folder}/ directory before processing. |
| dilation | int | 0 | Morphological dilation of the label mask in voxels before applying (clamped 0–100). |
| flagFilter | str | off | Batch subject filter for flagged scans: "off" (all), "exclude" (skip flagged), or "only" (flagged only). |
| propagate_linked | boolean | True | When true, reuse each main subject’s (already dilated) label mask on same-shape linked siblings. |
Algorithm / details
1) Enumerate voxel subjects in extracted/ (+ atlas), excluding faulty scans, preserved meshes, linked children, and applying flagFilter. 2) For each subject, find the latest full-res .nii.mask.gz (no _lossy). 3) Skip if no mask or label not in unique values. 4) Build label_mask = (mask == label), optionally dilate. 5) Multiply image by label_mask; save as edit_0 under masks/{mask_folder}/extracted/{subject}/. 6) Copy metadata (no lossy_compression), create lossy edit_0, copy/rename elastic transforms and masked elastic copies when present. 7) Emit progress_group WebSocket events. When propagate_linked, reuse the main’s final label mask on same-shape linked siblings.
I' = I \cdot \mathrm{dilate}(M = \ell,\, d)
ℓ: selected label; d: dilation radius in voxels; dilate expands the label before masking.
flowchart TD
A["All subjects"] --> B["Filter flags/meshes/faulty"]
B --> C["Latest full-res mask"]
C --> D{"Label present?"}
D -->|"no"| E["Skip"]
D -->|"yes"| F["Optional dilation"]
F --> G["Multiply + save edit_0"]
G --> H["Next subject"]
Related: apply-mask-to-image, batch-mask-out-scans, list-mask-folders
Transfer masks
Keywords: transfer masks, propagate mask, segmentation, labels, elastic deformation, atlas, nearest neighbor
Summary
Propagates segmentation masks between atlas, reference and subjects using the elastic deformation fields, warping them with nearest-neighbor interpolation so label values are preserved. Use it when masks exist on the reference or atlas and the subjects are elastically registered, or when a selected subject has a pre-elastic mask you want to pull back onto the reference. Modes: atlas→subjects, reference→subjects, reference→atlas, and subject→reference. Requires a voxel-based reference and skips preserved PLY meshes, faulty scans, and subjects without the required elastic deformation data.
Considerations
Why use it
Puts a segmentation on every subject automatically by carrying it from the reference or atlas along each subject's elastic deformation. You segment once and get matching labels on all subjects.
Use it when
- Elastic registration has finished and the reference or atlas has a mask.
- A subject has a mask drawn before elastic registration that you want on the reference (subject to reference).
Skip it when
- Elastic registration is poor for a subject; the transferred mask is only as good as the deformation.
Effect on later steps
- Apply masks (Helps): transferred labels can be used to isolate one structure in every subject.
- AI segmentation (Helps): a transferred label is a good starting region for AI refinement.
Why it works
The mask is warped with each subject's deformation using nearest-neighbour interpolation, so label numbers stay exact (no blending between labels). You can transfer one label and merge it into the existing mask, restrict the result to the above-threshold region, or refine it with an AI model.
Signs of poor parameters
- Restricting to the threshold when the label covers soft tissue below the threshold: those parts are removed.
- Transferring all labels onto subjects that already have hand-corrected masks: they are replaced. Use a single label to merge.
Good parameters
- Threshold restriction on for bone or other above-threshold structures; it trims the small overshoot from the deformation.
- Single-label transfer when subjects already have other labels you want to keep.
How to check the result
- Overlay the transferred masks on a few subjects with very different shapes and check the boundaries, especially at thin parts.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | | Project root directory containing extracted scan folders. |
| reference | string | | Reference scan name; must be a voxel volume. |
| transferType | string | | atlas-to-subjects, reference-to-subjects, reference-to-atlas, or subject-to-reference. |
| onlyCurrentSubject | boolean | False | Restrict the transfer to currentSubject. |
| currentSubject | string|null | | Subject used when onlyCurrentSubject is true, and the source subject for subject-to-reference. |
| selectedLabel | integer|null | | Transfer only this label and merge it into the existing destination mask (other labels there are kept); null transfers all labels. |
| doThreshold | boolean | False | After warping, keep mask voxels only where the destination image is above its threshold. |
| doAI | boolean | False | Optionally refine the warped mask with an AI segmentation model. |
| aiModel | string|null | | AI model used when doAI is true. |
Algorithm / details
Validate that the reference (and the current subject in single-subject mode) are voxel volumes. Collect eligible subjects for the chosen mode, excluding the reference, faulty scans and preserved meshes. For each target, pick the latest elastic forward or inverse displacement field for the direction of transfer, warp the source mask with ANTs nearest-neighbor interpolation, optionally merge a single selected label into the existing destination mask, optionally intersect with the above-threshold region and optionally refine with AI, then save the full-resolution mask and its paired lossy mask.
Related: transfer-landmarks, elastic-registration, batch-apply-mask-to-scans, create-atlas
Mask Out
Mask Out Labels
Keywords: mask out, delete, isolate, labels, edit, landmarks, invert
Summary
Creates a new in-place edit by zeroing voxels in the current image that match one or more mask labels (or, with invert=true, keeping only those voxels). Saves {filename}_edit_{N}_masked.nii.gz plus a lossy companion, copies and downsamples the mask to the new edit, and copies landmarks unchanged because geometry is not transformed.
Considerations
Why use it
Removes one or more labelled structures from the image (or keeps only them) as a new edit in the same subject, for example to remove a holder or a neighbouring bone before registration.
Use it when
- Something in the image (a holder, implant, neighbouring structure) disturbs later steps and is already labelled.
Skip it when
- You want a separate dataset rather than a new edit in the same subject; use Apply masks.
Effect on later steps
- Registration (Helps): removed structures no longer pull the alignment.
- Landmarks (No difference): geometry does not change, so landmarks are copied unchanged.
Why it works
Voxels with the chosen labels are set to zero (or, with invert, everything else is). The mask and landmarks are copied to the new edit unchanged.
Signs of poor parameters
- Masking out with a label that is drawn too tightly: a rim of the removed object remains.
How to check the result
- Check slices and the mesh: the object should be gone completely and nothing else removed.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject or scan folder name; use "atlas" for atlas data. |
| edit | str | filename | Current edit name (e.g. scan_edit_2_elastic.nii.gz). Defaults to filename when omitted. |
| labels | list[int] | required | Non-empty array of positive label integers to mask out (e.g. [1] or [1, 2, 3]). |
| mask_source | object|null | null | Optional {filename, edit} to read the mask from another scan so linked siblings clear the same voxels. |
| fullResolutionState | bool | false | Resolution context from the frontend (mask and image are always loaded at full resolution regardless). |
| invert | bool | false | False: zero labeled voxels (mask out). True: zero everything except labeled voxels (isolate). |
Algorithm / details
1) Resolve mask via mask_source when provided, else this scan’s mask; load full-res mask and image for edit. 2) Build combined_mask = OR(mask == label) for all labels; error if zero voxels. 3) If invert: zero ~combined_mask; else zero combined_mask. 4) Find next free edit number N. 5) Save {filename}_edit_{N}_masked.nii.gz. 6) Create lossy version via RegistrationTools.save_as_lossy_nifti. 7) Copy source mask to new edit; downsample lossy mask by resolution_factor stride slices. 8) Copy landmarks and landmark_distances JSON without transformation.
C = \bigvee_i (M = \ell_i)
C: combined label mask; ℓ_i: selected labels; ∨: OR over label indicators.
I' = I \cdot (C\ \mathrm{if\ invert\ else}\ \neg C)
invert: keep the labeled region; otherwise zero it out.
flowchart TD
A["labels + edit"] --> B["Load full mask + image"]
B --> C["combined_mask OR labels"]
C --> D{"invert?"}
D -->|"false"| E["Zero labeled voxels"]
D -->|"true"| F["Zero unlabeled voxels"]
E --> G["Save edit_N_masked + lossy"]
F --> G
G --> H["Copy mask + landmarks"]
Related: batch-mask-out-scans, save-mask, get-label-names
Batch Mask Out Scans
Keywords: mask out, batch, label, elastic, overwrite, dilation, flag filter
Summary
Runs mask-out on every eligible subject in extracted/ (and atlas) for a single label, creating new _masked edits in-place rather than in a mask subfolder. Handles elastic-edit insertion by bumping elastic files forward, supports optional dilation, and can overwrite an existing masked-before-elastic slot when overwrite=true. Skips mesh-only and flagged subjects per flagFilter.
Considerations
Why use it
Removes (or isolates) one labelled structure in every subject as a new edit, and can insert that edit before an existing elastic registration so the registration chain stays consistent.
Use it when
- A holder, implant or neighbouring structure is labelled in all subjects and should be removed from all of them.
Skip it when
- Not every subject has the label; those subjects stay unchanged and the cohort becomes inconsistent.
Why it works
In each subject, voxels with the label (optionally grown by a few voxels) are set to zero, or kept alone with invert. If the subject already has an elastic edit, the masked edit is inserted before it and the elastic copies are masked too.
Signs of poor parameters
- No dilation when the label is drawn tightly: a rim remains.
- Large dilation: tissue next to the removed object is lost.
Good parameters
- 1–2 voxels of dilation for removing objects; none when isolating, unless the label is drawn tightly.
How to check the result
- Scroll through the grid previews to confirm the object is removed in every subject.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory containing extracted/. |
| label | int | required | Mask label value (0–255) to mask out or isolate. |
| invert | bool | false | False: zero labeled voxels. True: keep only labeled voxels. |
| overwrite | bool | false | When true, replace an existing masked edit inserted before elastic using the prior edit as source; when false, skip subjects where that insertion already exists. |
| dilation | int | 0 | Morphological dilation of the label mask in voxels before applying (clamped 0–100). |
| flagFilter | str | off | Batch subject filter for flagged scans: "off", "exclude", or "only". |
Algorithm / details
1) Enumerate and filter subjects (faulty, mesh-only, linked children, flagFilter). 2) For each subject, detect next edit slot and whether latest edit is elastic or already masked. 3) Skip if masked insertion exists and overwrite=false. 4) Select source mask (latest, or edit before elastic/overwrite slot). 5) combined_mask = (mask == label), optional dilation; apply invert logic to image. 6) If elastic follows: bump elastic files E→E+1, insert masked at E, mask elastic copies in-place. 7) Save edit_N_masked + lossy, copy/downsample mask, copy landmarks. 8) Fix lossy_compression metadata after elastic bump. 9) Broadcast progress_group events.
C = \mathrm{dilate}(M = \ell,\, d)
C: dilated label region; ℓ: label; d: dilation.
I'(v) = 0\ \mathrm{where}\ (C\ \mathrm{xor\ invert})(v)
Zero voxels inside C (or outside C when invert is set).
flowchart TD
A["Each subject"] --> B{"Elastic is latest?"}
B -->|"yes"| C["Bump elastic + insert masked before"]
B -->|"no"| D["Use next edit slot"]
C --> E["Apply label mask to image"]
D --> E
E --> F["Save _masked edit + lossy"]
F --> G["Copy mask + landmarks"]
Related: mask-out-labels, batch-apply-mask-to-scans
Segmentation & Edges
AI Segmentation
Get AI Models
Keywords: segmentation, ai, medsam2, models, list
Summary
Returns the AI segmentation models the UI can offer: foundation MedSAM2 first, then any successfully registered fine-tuned checkpoints. Call this before RunAISegmentationView so the model picker stays in sync with the registry.
Considerations
Why use it
Lists the AI segmentation models you can use: the general MedSAM2 model and any models fine-tuned on your own data.
Use it when
- It runs automatically when you open the AI segmentation model picker.
How to check the result
- If a fine-tuned model is missing from the list, check that its training finished and it was registered.
Algorithm / details
GET handler with no request parameters. Calls list_model_descriptors(include_capabilities=True) and returns models (descriptor objects) plus count. On failure returns HTTP 500 with an error message.
flowchart TD
A["GET / GetAIModelsView"] --> B["list_model_descriptors"]
B --> C["Return models + count"]
Related: run-ai-segmentation, ai-segmentation, medsam2-segmentation
Run AI Segmentation
Keywords: segmentation, ai, medsam2, mask, label, nifti
Summary
Runs AI-assisted segmentation on a subject scan and merges the result into the existing mask at a chosen label. Loads the full-resolution NIfTI image and mask from the extracted subject folder (or atlas), dispatches to ai_segmentation, then saves both full- and lossy-resolution masks. Use when a user has seeded a rough label region and wants MedSAM2 to refine or expand it.
Considerations
Why use it
Uses AI to turn a rough label you painted into a full, accurate 3D segmentation of that structure, which is much faster than painting every slice.
Use it when
- You need to segment structures that a threshold cannot separate (soft tissue, touching bones, organs).
- You have a rough label (a few strokes or a transferred mask) and want it refined.
Skip it when
- A threshold already separates the structure cleanly; that is faster and fully reproducible.
Effect on later steps
- Apply masks and mask out (Helps): accurate labels make isolated-structure datasets clean.
- Transfer masks (Helps): segment the reference or atlas carefully once, then transfer.
Why it works
The region covered by your current label defines a bounding box and an example of what to segment. The model segments inside that box from three viewing directions, combines them, keeps the largest connected piece and writes it into your mask at that label only; other labels are untouched.
Signs of poor parameters
- A seed label that covers neighbouring structures: the model extends into them.
- A tiny seed in a large structure: the bounding box is too small and the result is cut off.
- Scans with very different intensity scales from the data a fine-tuned model was trained on.
Good parameters
- A seed that touches the structure's full extent in a few slices along each axis, without crossing into neighbours.
- Background Offset Correction and Intensity Normalization before AI work, so contrast is consistent across subjects.
Finding the best values
- Paint a few strokes covering the structure's extent.
- Run the model and look at the result in all three views.
- Correct leaks or gaps with the brush and run again if needed.
How to check the result
- Scroll through the slices in all three directions; check the boundaries where the structure touches its neighbours.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory containing extracted/ or atlas/ folders. |
| subject | str | required | Subject folder name or 'atlas' for atlas data. |
| edit | str | null | null | Edit filename (e.g. scan_lossy.nii.gz). When null, uses the raw subject image as base. |
| model | str | required | AI model id to run: foundation MedSAM2 or a registered fine-tuned checkpoint. |
| current_label | int | required | Label number (1–255) to segment and write into the mask. 0 is reserved for background. |
Algorithm / details
1) Validate required POST fields and coerce current_label to int in range 1–255. 2) Resolve base_path (extracted/{subject} or atlas/). 3) Load {full_edit}.nii.gz image and existing or empty {full_edit}.nii.mask.gz mask (via temporary _mask.nii.gz rename for nibabel). 4) Call ai_segmentation(image, mask, current_label, model). 5) Save merged mask at full resolution, then downscale to lossy resolution with scipy.ndimage.zoom (order=0). 6) Return message, segmentation_label, voxels_segmented, and model_used.
flowchart TD
A["POST RunAISegmentationView"] --> B["Validate params"]
B --> C["Load image + mask NIfTI"]
C --> D["ai_segmentation"]
D --> E["Merge at current_label"]
E --> F["Save full mask"]
F --> G["Scale + save lossy mask"]
G --> H["Return stats"]
Related: get-ai-models, ai-segmentation, medsam2-segmentation, save-mask
AI Segmentation Dispatcher
Keywords: segmentation, ai, medsam2, dispatch, mask
Summary
Internal dispatcher that routes AI segmentation requests through the model registry. Resolves foundation MedSAM2 or a registered fine-tuned MedSAM2 checkpoint, then runs prompt-guided inference. Raises if the model id is unknown or is not a prompt-guided MedSAM2 foundation. Called by RunAISegmentationView after image and mask volumes are loaded.
Considerations
Why use it
Internal step that sends an AI segmentation request to the right model (the general MedSAM2 model or one of your fine-tuned models).
Use it when
- It runs automatically behind Run AI Segmentation.
How to check the result
- An unknown model error usually means a fine-tuned model was removed or not registered.
Inputs / options
| Name | Type | Default | Description |
|---|
| image_data_3d | np.ndarray | required | 3D medical image volume. Shape (width, height, depth) or (D, H, W); typically uint8 or float32. |
| mask_data_3d | np.ndarray | required | 3D label mask volume, same shape as image. dtype uint8; 0 = background. |
| target_label | int | required | Label number to segment (1–255). |
| model | str | required | Model id: foundation MedSAM2 or a registered fine-tuned checkpoint id. |
Algorithm / details
Dispatch through aiModels.registry.run_inference: resolve the descriptor, reject unknown or non-prompt-guided models, then run MedSAM2 inference with that checkpoint. Returns the merged 3D mask array.
flowchart TD
A["ai_segmentation"] --> B["run_inference"]
B --> C{"Known prompt-guided MedSAM2?"}
C -->|"yes"| D["run_medsam2_inference"]
C -->|"no"| E["Raise unsupported model error"]
D --> F["Return result_mask"]
Related: run-ai-segmentation, medsam2-segmentation, get-ai-models
MedSAM2 Segmentation
Keywords: medsam2, segmentation, bounding box, ensemble, mask
Summary
Runs MedSAM2 on a 3D volume using the existing mask voxels at target_label to define a bounding box and binary reference mask. Segments within that region, keeps the largest connected component, uses a 3-view ensemble, then merges the result back into the mask at target_label only. Returns the original mask unchanged if no voxels carry the target label.
Considerations
Why use it
The MedSAM2 model step itself: it segments a 3D structure guided by the region of your existing label.
Use it when
- It runs automatically behind Run AI Segmentation when MedSAM2 or a MedSAM2-based fine-tuned model is selected.
Why it works
Your label's bounding box and shape prompt the model. It segments inside the box from three directions, the three results are combined, and only the largest connected piece is kept and written to the target label.
Signs of poor parameters
- Running it on a label with no voxels: nothing happens, the mask is returned unchanged.
How to check the result
- Check the result in all three views; ensemble artefacts usually show up as missing slabs along one direction.
Inputs / options
| Name | Type | Default | Description |
|---|
| image_data_3d | np.ndarray | required | 3D image volume, shape (D, H, W). |
| mask_data_3d | np.ndarray | required | 3D mask volume, shape (D, H, W), dtype uint8. |
| target_label | int | required | Label whose voxels define the bounding box and reference region. |
Algorithm / details
1) Find all voxels where mask == target_label; compute axis-aligned bounding box (d_min..d_max, h_min..h_max, w_min..w_max) in (z,y,x) order. 2) Build binary reference_mask (1 at target_label, 0 elsewhere). 3) Instantiate MedSAM2Segmenter(force_cpu=False). 4) Call segmenter.segment with bbox_min/bbox_max, reference mask, use_largest_cc=True, existing_mask_as_reference=True, ensemble=True. 5) Copy mask and set result_mask[segmentation_mask==1] = target_label.
L = \{(d,h,w) \mid M_{dhw} = \ell\}
L: voxels of the prompt label ℓ in mask M.
b_{\min} = \min L
Lower corner of the axis-aligned bounding box over label voxels L.
b_{\max} = \max L
Upper corner of the box used as the MedSAM2 spatial prompt.
flowchart TD
A["Find target_label voxels"] --> B{"Bbox empty?"}
B -->|"yes"| C["Return mask copy"]
B -->|"no"| D["Build reference binary mask"]
D --> E["MedSAM2Segmenter.segment"]
E --> F["Merge into result_mask at target_label"]
F --> G["Return result_mask"]
Related: ai-segmentation, run-ai-segmentation
Edge Detection
Edge Detection Slice Preview
Keywords: edge, hysteresis, preview, patch, morphology
Summary
Fast 2D slice preview of hysteresis edge detection on a 100³ patch centered at (x,y,z). Uses the same global volume min/max normalization as the full mesh pipeline so slider tuning matches the final result. Caches raw patch and v_min/v_max per scan/edit/position/resolution (max 3 entries).
Considerations
Why use it
Shows the effect of the edge-detection settings on one slice instantly, so you can tune them before running the whole volume.
Use it when
- Always before running Edge Detection, and whenever you change thresholds or closing.
Why it works
A 100-voxel cube around the chosen point is processed with the same normalization as the full run, and the middle slice is shown at each stage: edges, closed, filled.
Good parameters
- A preview position at the thinnest or faintest part of the structure you want to capture.
How to check the result
- Check two or three positions (thick, thin and faint regions) before running the full volume.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject filename or 'atlas'. |
| edit | str | optional | Edit NIfTI filename; used to resolve volume path. |
| full_resolution | bool | false | Load full-resolution volume instead of lossy. |
| x | float | 0 | Patch center X coordinate in volume indices. |
| y | float | 0 | Patch center Y coordinate in volume indices. |
| z | float | 0 | Patch center Z coordinate in volume indices. |
| low | float | 0.10 | Hysteresis low threshold on normalized [0,1] intensities. |
| high | float | 0.30 | Hysteresis high threshold on normalized [0,1] intensities. |
| closing_radius | int | 3 | Radius (voxels) of the spherical structuring element for binary closing. |
Algorithm / details
1) Load or cache 100³ patch and global v_min/v_max. 2) Normalize patch: (patch - v_min)/(v_max - v_min). 3) apply_hysteresis_threshold(low, high). 4) binary_closing with morphology.ball(closing_radius). 5) Return flat uint8 center-Z slices for raw, edges, closed, and filled stages plus patch/volume intensity stats.
\hat{v} = (v - v_{\min}) / (v_{\max} - v_{\min})
Per-slice min–max normalization before the edge detector.
\mathrm{keep}\ \hat{v} \ge t_{\mathrm{high}}\ \mathrm{or\ connected\ through}\ \hat{v} \ge t_{\mathrm{low}}
Hysteresis: strong edges (≥ t_high) plus weaker edges (≥ t_low) connected to them.
flowchart TD
A["POST slice preview"] --> B["Load/cache 100³ patch"]
B --> C["Global min-max normalize"]
C --> D["Hysteresis threshold"]
D --> E["Binary closing ball r"]
E --> F["Return center-Z slices flat"]
Related: edge-detection
Edge Detection
Keywords: edge, hysteresis, marching cubes, mesh, mask, morphology
Summary
Full-volume edge detection pipeline: hysteresis thresholding, morphological closing, optional largest-component filtering, then either preview GLB mesh via marching cubes or save the result as a segmentation mask label. When saving permanently from a lossy preview, morphological parameters are scaled up to full resolution. Supports preview-only mesh generation or mask export with force_new_label.
Considerations
Why use it
Builds a surface or mask from edges rather than from a single threshold. It helps when the structure's brightness varies too much for one threshold, for example faint thin walls next to dense regions.
Use it when
- One threshold either misses faint parts or includes noise elsewhere.
- You need a closed, filled shape from a structure whose interior is not uniformly bright.
Skip it when
- A single threshold already captures the structure cleanly.
Effect on later steps
- Masks and AI segmentation (Helps): saved as a mask label, the result can seed AI refinement or be applied to the image.
Why it works
Intensities are scaled to 0–1. Hysteresis thresholding keeps strong edges (above the high value) and any weaker edges (above the low value) connected to them, which keeps faint parts that belong to a strong structure while dropping isolated noise. Morphological closing then bridges small gaps, the shape is filled, and small or secondary pieces can be removed.
Signs of poor parameters
- A low value too low: noise connected to the structure is kept, and the shape bleeds into the background.
- A high value too high: faint parts of the structure are never seeded and disappear.
- A large closing radius: separate structures and real holes are closed over.
Good parameters
- High just above the brightest noise, low just below the faintest real wall.
- The smallest closing radius that seals the gaps you see in the preview.
Finding the best values
- In the slice preview, raise the high value until noise is no longer seeded.
- Lower the low value until faint walls are fully traced.
- Increase closing until the outline is closed, and no further.
- Preview the 3D mesh before saving it as a mask.
How to check the result
- Compare the result with slices at thin and faint regions; check that real holes and gaps remain open.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Root data directory. |
| filename | str | required | Subject filename or 'atlas'. |
| edit | str | optional | Edit NIfTI filename. |
| full_resolution | bool | false | Use full-resolution volume for processing. |
| preview_only | bool | true | When true with save_as_mask false, return GLB mesh preview only. |
| force_new_label | bool | false | When saving mask and one exists, allocate a new label instead of returning mask_already_exists. |
| save_as_mask | bool | false | Save binary result as segmentation mask instead of returning mesh. |
| low | float | 0.10 | Hysteresis low threshold on normalized intensities. |
| high | float | 0.30 | Hysteresis high threshold on normalized intensities. |
| closing_radius | int | 3 | Binary closing ball radius in voxels. |
| largest_component_mode | str | largest | Component filter: 'off', 'largest' (keep single largest CC), or 'above_min' (remove objects smaller than min_size). |
| min_size | int | 1000 | Minimum connected-component volume in voxels for debris removal. |
| fill_mesh | bool | true | When true, march cubes on the closed/filled binary mask; when false, march cubes on raw hysteresis edges. |
Algorithm / details
1) Load volume and metadata via _load_volume_and_meta. 2) If save_as_mask and not preview_only and not full_resolution, scale closing_radius by resolution_factor and min_size by resolution_factor³. 3) _run_pipeline: normalize, hysteresis, binary closing, optional largest-component or above_min filtering. 4a) Mask path: write .nii.mask.gz with new or merged label; mirror to companion resolution. 4b) Mesh path: swap axes for Babylon, marching_cubes(level=0.5), center and normalize verts, decimate if >250k vertices, return base64 GLB with scale_factor and center.
r' = r \cdot f
r: radius (or length) measured on the lossy preview; f: resolution_factor; r': full-resolution length.
s' = s \cdot f^{3}
s: volume measured on lossy voxels; s': physical/full-res scaled volume (cubic in f).
flowchart TD
A["POST EdgeDetectionView"] --> B["Load volume + spacing"]
B --> C["Scale params if lossy→full save"]
C --> D["Hysteresis + closing + CC filter"]
D --> E{"save_as_mask?"}
E -->|"yes"| F["Write mask label"]
E -->|"no"| G["Marching cubes → GLB"]
F --> H["Write companion resolution mask"]
G --> I["Return gltf + scale + center"]
Related: edge-detection-slice-preview, marchingcubes
Resampling
Deformation-Based MRI Interpolator
Keywords: resample, anisotropic, isotropic, ants, deformation, mri
Summary
Class that converts anisotropic MRI volumes to isotropic voxel spacing using bidirectional ANTs slice registration. Requires exactly one thick axis and two equal thin axes (within 1% tolerance). Used during scan extraction in views.py when anisotropy ratio exceeds 1.1. Default registration_type is SyNOnly for performance.
Considerations
Why use it
Fills in the missing slices of thick-slice MRI by deforming neighbouring slices into each other, which keeps edges sharp instead of blurring them as ordinary interpolation does.
Use it when
- It runs automatically during extraction for MRI whose slices are much thicker than the in-plane voxel size.
Skip it when
- Scans with near-isotropic voxels; nothing needs interpolating.
Effect on later steps
- Every 3D step (Helps): thresholds, meshes and registration work on a volume with equal spacing in all directions.
Why it works
For each pair of neighbouring slices, the deformation from one to the other is computed in both directions. Each new slice in between is made by moving both neighbours part of the way and blending them by distance.
Signs of poor parameters
- Very large gaps between slices (thick slices relative to anatomy): structures that change shape a lot between slices can be invented or smeared.
How to check the result
- Look at the result in the direction across the slices: edges should be continuous without stair-steps or ghosting.
Inputs / options
| Name | Type | Default | Description |
|---|
| registration_type | str | SyNOnly | ANTs registration method: 'SyNOnly' (elastic only), 'SyN', 'Affine', or 'Rigid'. |
| verbose | bool | false | Enable progress and diagnostic print output. |
Algorithm / details
Constructor stores registration_type and verbose. Main entry is interpolate_anisotropic_to_isotropic (see related entry). Internally: auto-detect thick axis as argmax(voxel_size); validate single maximum and equal thin axes; compute target isotropic size as min(thin axes); upsample slice count by scale_factor = thick_size / target_size; for each adjacent slice pair precompute A→B and B→A ANTs SyN deformations; for each target slice position interpolate by scaled deformations and distance-weighted blend of moved A and B slices.
f = s_{\mathrm{thick}} / s_{\mathrm{iso}}
Scale factor from thick-slice spacing to isotropic (min in-plane) spacing; in-plane axes must agree within 1%.
N' = \mathrm{round}(N \cdot f)
N: original thick-axis slice count; N′: interpolated count along that axis.
w_A = (p - p_A)/(p_B - p_A),\quad w_B = 1 - w_A
Same blend weights as interpolate_anisotropic_to_isotropic (paper §2.2.2).
I(p) = w_B\, A_{\mathrm{moved}} + w_A\, B_{\mathrm{moved}}
Warped-neighbor blend after bidirectional SyNOnly between A and B.
flowchart TD
A["DeformationBasedMRIInterpolator"] --> B["interpolate_anisotropic_to_isotropic"]
B --> C["Detect thick axis + validate"]
C --> D["Precompute pair deformations ANTs SyN"]
D --> E["For each target slice position"]
E --> F["Scale + apply A→B / B→A fields"]
F --> G["Blend A_moved and B_moved"]
G --> H["Isotropic volume"]
Related: interpolate-anisotropic-to-isotropic, interpolate-mri-bidirectional
Interpolate Anisotropic to Isotropic
Keywords: resample, anisotropic, isotropic, deformation, slice interpolation
Summary
Converts anisotropic MRI (thick-slice) volumes to isotropic spacing by bidirectional ANTs SyNOnly registration between adjacent slices and distance-weighted blending of the warped neighbors (Ehrhardt-style), rather than simple trilinear smoothing.
Considerations
Why use it
Turns thick-slice MRI into equal spacing in all directions by registering neighbouring slices to each other, giving sharper results than standard interpolation.
Use it when
- It runs during extraction for MRI with one thick axis and two equal thin axes.
Skip it when
- The two in-plane voxel sizes differ by more than 1 %; the method requires them equal.
Why it works
The thick axis is found automatically. New slices are placed at the in-plane spacing; each is made by warping the two neighbouring slices part of the way towards each other and blending them by distance.
Signs of poor parameters
- Slices so thick that neighbouring slices show different structures: the deformation cannot match them and the result can look smeared.
How to check the result
- Check sagittal or coronal views across the original slices for smooth, continuous edges.
Inputs / options
| Name | Type | Default | Description |
|---|
| image | np.ndarray | required | Input 3D MRI volume. |
| voxel_size | Tuple[float, float, float] | required | Voxel dimensions (x, y, z) in mm. |
| target_axis | int | None | null | Axis index (0, 1, or 2) to interpolate. When null, auto-selects the axis with largest voxel size. |
Algorithm / details
1) Identify the thick axis as argmax(voxel_size); require the two in-plane spacings to match within 1% tolerance; target spacing = min in-plane size. 2) For each adjacent pair (A, B), compute bidirectional SyNOnly fields (cross-correlation; reg_iterations = (200, 100, 100, 100), grad_step = 0.2, flow_sigma = 3, singleprecision = True). 3) For each intermediate physical position p, set weight_A = (p − p_A)/(p_B − p_A), weight_B = 1 − weight_A; warp A with the A→B field scaled by weight_A and B with B→A scaled by weight_B; blend I(p) = A_moved·weight_B + B_moved·weight_A (RegularGridInterpolator). 4) Stack intermediates into an isotropic volume.
w_A = (p - p_A)/(p_B - p_A),\quad w_B = 1 - w_A
Fractional weights for target position p between adjacent original slices at p_A and p_B.
I(p) = w_B\, A_{\mathrm{moved}} + w_A\, B_{\mathrm{moved}}
Bidirectional blend of slices warped by fractionally scaled SyNOnly fields; avoids bias toward either neighbor.
flowchart TD
A["3D anisotropic image"] --> B["Validate axis anisotropy"]
B --> C["Compute new slice grid"]
C --> D["Register adjacent slice pairs ANTs"]
D --> E["Scale deformation fields by distance weight"]
E --> F["RegularGridInterpolator sample + blend"]
F --> G["Isotropic 3D output"]
Related: deformation-based-mri-interpolator, interpolate-mri-bidirectional
Interpolate MRI Bidirectional
Keywords: resample, convenience, anisotropic, isotropic, ants
Summary
Module-level convenience wrapper that constructs a DeformationBasedMRIInterpolator and calls interpolate_anisotropic_to_isotropic, casting the result back to the input dtype. Default registration_type is 'SyN' (full SyN) and verbose is true, unlike the class default used in views.py.
Considerations
Why use it
A convenience entry point to the same slice-registration interpolation, mainly for scripts and testing.
Use it when
- You call the interpolation from your own Python code.
Why it works
It creates the interpolator (full SyN by default) and returns the isotropic volume in the input data type.
Inputs / options
| Name | Type | Default | Description |
|---|
| image_array | np.ndarray | required | 3D numpy array containing MRI data. |
| voxel_sizes | Tuple[float, float, float] | required | Tuple of (x, y, z) voxel sizes in mm. |
| registration_type | str | SyN | ANTs transform type: 'SyN' (recommended), 'Affine', or 'Rigid'. |
| verbose | bool | true | Enable progress output during interpolation. |
Algorithm / details
interpolator = DeformationBasedMRIInterpolator(registration_type, verbose); return interpolator.interpolate_anisotropic_to_isotropic(image_array, voxel_sizes).astype(image_array.dtype).
flowchart TD
A["interpolate_mri_bidirectional"] --> B["Create DeformationBasedMRIInterpolator"]
B --> C["interpolate_anisotropic_to_isotropic"]
C --> D["Cast to input dtype"]
D --> E["Return isotropic array"]
Related: deformation-based-mri-interpolator, interpolate-anisotropic-to-isotropic
Mesh Tools
Preserved Mesh Editing
Mesh Edit Mixin
Keywords: preserved mesh, ply edit, landmarks, metadata, edit chain, trimesh
Summary
Shared backend mixin for preserved PLY scan edits. Resolves source meshes from the edit chain on disk (filename_edit_N_operation.ply), loads and validates Trimesh geometry, writes new edits through PreservedMeshEditWriter (which also refreshes quick-grid projection PNGs), and propagates landmark JSON plus distance sidecars whenever topology or coordinates change. Edit filenames are the source of truth; the scan JSON stores mesh summaries and elastic metadata but not a full edit list.
Considerations
Why use it
The shared machinery behind every preserved-mesh edit (crop, slice, rotation, cleanup, shell, snap): it finds the right source mesh, saves the new edit and carries landmarks along, so every mesh tool behaves the same way.
Use it when
- It runs automatically behind each mesh edit tool.
Why it works
Edits are numbered files (subject_edit_N_operation.ply); the highest number is the latest. Each tool reads the latest (or a chosen) edit, writes the next number, and moves the landmark files to the new edit.
How to check the result
- If a mesh tool seems to start from the wrong state, check the edit timeline: tools use the latest edit unless you pick one.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory containing extracted/. |
| filename | string | required | Scan stem name (folder under extracted/). |
| edit | string | null | latest | Specific PLY basename to use as input, or "latest" for the highest-numbered _edit_* PLY. |
Algorithm / details
1) Locate subject dir at extracted/{filename}/ and load {filename}.json metadata. 2) Resolve source PLY via edit argument or latest glob match on {filename}_edit_*_*.ply. 3) Load geometry with trimesh (concatenate multi-body scenes). 4) After an operation, allocate the next edit number, write {filename}_edit_{N}_{operation}.ply via PreservedMeshEditWriter.save, update landmarks for the new stem when present, and append mesh_summary to JSON metadata.
flowchart TD
A["HTTP ApplyMesh* request"] --> B["Load metadata JSON"]
B --> C["Resolve source PLY edit"]
C --> D["Load Trimesh"]
D --> E["Run mesh operation"]
E --> F["PreservedMeshEditWriter.save"]
F --> G["Propagate landmarks"]
G --> H["Update scan JSON metadata"]
Related: preserved-mesh-edit-writer, apply-mesh-crop, apply-mesh-slice, apply-mesh-rotation, apply-mesh-cleanup, alpaca-get-outer-mesh, apply-mesh-shell, apply-mesh-snap-to-mesh
Preserved Mesh Edit Writer
Keywords: ply export, welded mesh, projection png, quick grid, mesh grid
Summary
Canonical export path for every preserved-mesh PLY edit. Welds coincident vertices so downstream tools see one connected surface, writes the PLY to disk, then regenerates front and back isometric projection PNGs used by the quick-access mesh grid. All ApplyMesh* endpoints and mesh elastic registration save through this writer.
Considerations
Why use it
Saves every preserved-mesh edit in one consistent way: it joins duplicated vertices so the surface is connected, writes the PLY and refreshes the preview images.
Use it when
- It runs automatically whenever a mesh tool saves an edit.
Why it works
Duplicated vertices on shared edges are merged before saving, then front and back preview images are rendered for the grid.
Inputs / options
| Name | Type | Default | Description |
|---|
| mesh | trimesh.Trimesh | required | Mesh geometry to persist. |
| output_path | string | required | Absolute or relative path for the output .ply file. |
Algorithm / details
1) ensure_welded_trimesh merges duplicate vertex indices along shared edges. 2) Export PLY with trimesh. 3) Call ensure_mesh_projection_pngs(output_path, force=True) to render transparent front/back isometric PNGs beside the PLY.
flowchart LR
A["Trimesh"] --> B["Weld vertices"]
B --> C["Export PLY"]
C --> D["ensure_mesh_projection_pngs"]
D --> E["Front + back PNG pair"]
Related: mesh-edit-mixin, ensure-mesh-projection-pngs, generate-mesh-grid-preview-jpeg
Ensure Mesh Projection PNGs
Keywords: projection, quick grid, thumbnail, vtk render, preserved mesh
Summary
Creates or refreshes the front ({stem}_projection.png) and back ({stem}_projection_back.png) thumbnail pair for a preserved-mesh PLY when missing or stale. Skips work when PNG mtimes are newer than the PLY unless force=True.
Considerations
Why use it
Keeps the front and back preview images of a mesh subject up to date for the grid and subject cards.
Use it when
- It runs automatically when previews are missing or older than the mesh.
Inputs / options
| Name | Type | Default | Description |
|---|
| ply_path | string | required | Path to the preserved-mesh .ply file. |
| force | boolean | false | When true, regenerate PNGs even if they appear up to date. |
Algorithm / details
1) Compare PLY mtime against both projection PNGs; return early if current. 2) load_centered_preserved_mesh for display-normalized vertices. 3) Render isometric VTK screenshots (opposite view signs for front vs back). 4) Save composited RGBA PNGs next to the PLY.
Related: preserved-mesh-edit-writer, generate-mesh-grid-preview-jpeg
Generate Mesh Grid Preview JPEG
Keywords: grid preview, jpeg, quadrant, axial, sagittal, coronal
Summary
Builds a 2×2 JPEG mosaic (axial, isometric, sagittal, coronal orthographic renders) from a preserved-mesh PLY for richer grid or preview responses. Each quadrant is rendered at QUADRANT_RENDER_SIZE then pasted into a single composite image.
Considerations
Why use it
Renders the four-view preview image (top, isometric, side, front) shown for mesh subjects in the grid.
Use it when
- It runs automatically for mesh subjects in grid views.
Inputs / options
| Name | Type | Default | Description |
|---|
| ply_path | string | required | Path to the preserved-mesh .ply file to preview. |
Algorithm / details
1) load_centered_preserved_mesh. 2) Render four orthographic/isometric VTK screenshots with fixed camera setups. 3) Resize each to a common quadrant size preserving aspect ratio. 4) Composite into a 2×2 RGB image and encode as JPEG bytes (GRID_PREVIEW_JPEG_QUALITY).
flowchart TD
PLY["PLY path"] --> C["Centered mesh"]
C --> Q1["Axial"]
C --> Q2["Isometric"]
C --> Q3["Sagittal"]
C --> Q4["Coronal"]
Q1 --> G["2x2 JPEG mosaic"]
Q2 --> G
Q3 --> G
Q4 --> G
Related: ensure-mesh-projection-pngs, preserved-mesh-edit-writer
Mesh surface utilities
Outer shell extraction
Keywords: outer shell, outer surface, visibility voting, get_outer_mesh, ALPACA.py, apply-mesh-shell, QuickMeshCanvas, refresh menu, Hidden Point Removal, raycasting, mesh tools
Summary
Universal visibility-based outer-surface extractor implemented as ALPACA.get_outer_mesh in ALPACA.py. Keeps only externally visible triangles so internal cavities, nested marching-cubes sheets, and double walls cannot pollute downstream surface work. The helper lives in ALPACA.py for historical reasons, but it is not exclusive to the ALPACA rigid-alignment workflow — Aurora reuses it wherever a clean exterior shell is needed.
Primary UI entry: Quick mesh canvas cleanup/refresh menu → Outer shell (POST /apply-mesh-shell/), which persists _shell.ply (preserved mesh) or _shell.nii.gz (voxel). Also invoked when Align to reference runs with outer_surface=True, landmark snap / transfer with use_outer_shell, semi-landmark placement on the cranial shell, mesh elastic complex_mesh_volume / auto-extract presets, and related surface tools.
Considerations
Why use it
Keeps only the outside surface of a mesh, removing internal cavities, double walls and nested sheets. Many surface tools give wrong answers when they can also match or snap to inner surfaces.
Use it when
- Landmarks snap to inner walls, or alignment matches inner structures.
- Marching-cubes meshes of hollow or porous structures (skulls, shells) with inner surfaces you do not study.
Skip it when
- You study inner surfaces (endocasts, cavities, canals); the shell removes them.
Effect on later steps
- Align to reference (ALPACA) (Helps): matching uses outside geometry only.
- Landmark snapping and transfer (Helps): points cannot jump to an inner wall.
- Mesh elastic registration (Helps): correspondences are not confused by nested sheets.
Why it works
The mesh is viewed from many directions around it. Triangles that are the first thing seen from enough directions are kept; triangles that are never seen from outside are removed. Tiny leftover pieces are removed and small holes healed.
Signs of poor parameters
- Deep, narrow openings (for example orbits or foramina): their inner walls are seen from few directions and may be partly removed.
How to check the result
- Compare the shell with the original from several angles, and look into deep openings to check what was kept.
Inputs / options
| Name | Type | Default | Description |
|---|
| vertices | np.ndarray (N, 3) | required | Input mesh vertices (preserved PLY or marching-cubes surface from a voxel edit). |
| faces | np.ndarray (M, 3) | required | Input mesh triangle indices. |
| invert_normals | bool | False | Flip winding / normals. Used automatically as a fallback when the first pass removes nearly all geometry (orientation was reversed). |
Algorithm / details
Shared mesh utility (not an ALPACA-only alignment stage). Called from Apply Mesh Shell, Align to reference (outer_surface), snap/transfer paths (use_outer_shell), mesh elastic presets, semi-landmarks, and other surface tools:
1) Weld coincident vertices so triangle adjacency is real (exported PLYs often duplicate verts).
2) Multi-view visibility (method 4): sample viewpoints on a bounding sphere; for each triangle, cast a ray through its centroid and count views where it is the first hit (Open3D RaycastingScene).
3) The vote histogram is typically bimodal — place the cutoff at the first valley so concavities visible from only some views are retained.
4) Prune tiny connected components and heal small boundary holes; return cleaned vertices/faces.
5) If reduction is extreme (>98% verts lost) and normals were not inverted, retry with invert_normals=True.
Older single-ray / normal-voting methods remain in ALPACA.py but are not selected.
Hidden Point Removal (inspiration) — Literature — Katz et al., 2007: Katz et al., 2007 — visibility idea; Aurora uses multi-view mesh voting instead of per-view HPR on clouds. (https://doi.org/10.1145/1275808.1276407)
Open3D RaycastingScene — Open3D — open3d.t.geometry.RaycastingScene: First-hit ray queries used for multi-view triangle visibility votes. (https://www.open3d.org/docs/latest/python_api/open3d.t.geometry.RaycastingScene.html)
flowchart TD
A["PLY or voxel MC triangle mesh"] --> B["Weld coincident verts"]
B --> C["Viewpoints on bounding sphere"]
C --> D["Per-triangle first-hit vote count"]
D --> E["Cutoff at first histogram valley"]
E --> F["Prune islands + heal holes"]
F --> G["Outer shell verts/faces"]
G --> H["Callers: Apply Mesh Shell · Align outer_surface · snap · elastic · semi-landmarks"]
Related: apply-mesh-shell, apply-mesh-snap-to-mesh, align-to-reference, alpaca-align-landmarks-to-mesh, alpaca-create-landmarks-from-mesh, get-semi-landmarks, snap-to-mesh, mesh-elastic-registration
Mesh Edit HTTP Operations
Apply Mesh Crop
Keywords: crop, bounding box, clip, reorigin, preserved mesh, POST apply-mesh-crop
Summary
Clips a preserved-mesh PLY to an axis-aligned bounding box and saves a new _cropped.ply edit. Expands the box by optional padding, re-originates surviving vertices so the crop minimum becomes the new origin, and drops landmarks outside the box while re-originating kept positions.
Considerations
Why use it
Cuts a mesh down to a box, removing parts you do not study (stands, neighbouring structures, scan borders), which makes alignment and registration focus on the region of interest.
Use it when
- The mesh includes holders, support material or regions outside your study.
Skip it when
- The unwanted part is not box-shaped; use Apply Mesh Slice for free-form cuts.
Effect on later steps
- Align to reference and elastic registration (Helps): removed parts no longer pull the alignment.
Why it works
Triangles outside the box (plus optional padding) are removed, coordinates are shifted so the box corner becomes the origin, and landmarks outside the box are dropped.
Signs of poor parameters
- A box that cuts through anatomy you study, or crops subjects inconsistently so they cover different regions.
How to check the result
- Check that all landmarks you need are still present and the cut edges are where you intended.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| filename | string | required | Scan stem to crop. |
| edit | string | null | null | Source PLY basename; omit to use metadata mesh_file or latest edit. |
| crop | number[6] | required | Two corners [x0,y0,z0,x1,y1,z1] in mesh mm; min/max are taken automatically. |
| padding | number | 0 | Extra mm added outward on every axis before clipping. |
Algorithm / details
1) Load source mesh. 2) Compute mins/maxs from crop corners ± padding. 3) Clip faces outside the box and shift vertices with reorigin_mesh_coords_after_mm_crop. 4) Write {filename}_edit_{N}_cropped.ply. 5) Filter and re-origin landmarks; copy subset of landmark distances.
m = \min(c_{0:3}, c_{3:6}) - p
m: padded lower corner; c: crop-box corners; p: padding.
M = \max(c_{0:3}, c_{3:6}) + p
M: padded upper corner of the retained axis-aligned bounds.
v' = v - m + p
After clipping, vertices are recentered into the padded crop frame.
Related: mesh-edit-mixin, apply-mesh-slice
Apply Mesh Slice
Keywords: slice, slicer, isolate, delete, plane clip, preview, POST apply-mesh-slice
Summary
Interactive slicer for preserved meshes and voxel volumes. Accepts a list of wedge operations (convex volumes defined by plane sets from volumes or outline rays). For preserved meshes, save=false returns a GLB preview without writing; save=true (or commit=true) writes _sliced.ply or _sliced.nii.gz depending on scan type. Landmarks are kept or removed according to the same wedge rules as geometry.
Considerations
Why use it
Removes or isolates parts of a mesh or volume with free-form cutting shapes drawn in the 3D view, for cuts a box cannot make.
Use it when
- You need to separate a structure from its neighbours along an irregular boundary, or remove a support or artefact.
Why it works
Each shape you draw defines a convex cutting volume. Isolate keeps what is inside the volumes; delete removes it. A preview is shown before anything is saved; landmarks follow the same rule as the geometry.
Signs of poor parameters
- Cutting volumes drawn from a single viewpoint on complex shapes: they extend through the whole depth and can remove parts hidden behind.
Good parameters
- Preview from more than one angle before saving.
How to check the result
- Rotate the preview to confirm nothing hidden behind the cut was removed.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| filename | string | required | Scan stem. |
| edit | string | null | null | Source edit basename. |
| operations | object[] | required | Non-empty list of slicer operations (see action, volumes, outline_points, outline_rays). |
| save | boolean | false | When true, persist a sliced edit. Aliased by commit. |
| commit | boolean | false | Truthy alias for save. |
| operations[].action | string | required | "isolate" keeps geometry inside any wedge; "delete" removes geometry inside wedges. |
| operations[].volumes | object[] | optional | Each volume supplies rays converted to plane sets; alternative to outline_points/outline_rays. |
| operations[].outline_points | number[][] | optional | Outline vertex indices paired with outline_rays when volumes is omitted. |
| operations[].outline_rays | object[] | optional | Ray definitions parallel to outline_points; triangulated into wedge plane sets. |
Algorithm / details
For each operation: build plane sets from volumes or triangulated outline rays. isolate: clip mesh to each wedge and concatenate survivors; delete: subtract convex volumes from the mesh. Repeat sequentially. Mesh path uses view-projected plane clipping; voxel path masks foreground voxels in physical mm. Preview mode returns decimated GLB via _mesh_preview_payload without writing files.
p \in W \iff \forall i:\ (p - o_i)\cdot n_i \ge 0
W: wedge kept by the slice; o_i, n_i: origin and inward normal of plane i.
flowchart TD
R["operations list"] --> A{"action"}
A -->|"isolate"| I["Clip to wedge union"]
A -->|"delete"| D["Subtract wedges"]
I --> N["Next operation"]
D --> N
N --> S{"save?"}
S -->|"no"| P["GLB preview"]
S -->|"yes"| W["Write _sliced edit"]
Related: mesh-edit-mixin, apply-mesh-crop
Apply Mesh Rotation
Keywords: rotation, babylon, manual align, preserved mesh, POST apply-mesh-rotation
Summary
Applies a manual rotation from the Babylon 3D viewer to a preserved-mesh PLY and saves _rotated.ply. Converts the Babylon 3×3 display matrix (Y/Z axis flip relative to stored PLY coordinates) into raw mesh space, rotates vertices about quick_mesh_properties.center (or surface centroid fallback), and rotates landmark positions identically.
Considerations
Why use it
Rotates a mesh into a chosen orientation (for example a standard anatomical pose), together with its landmarks.
Use it when
- Meshes come in very different poses and you want a consistent starting orientation, or a standard pose for figures.
Skip it when
- Align to reference will run anyway and you do not need a particular pose.
Effect on later steps
- Align to reference (Helps): a roughly consistent pose makes rigid alignment faster and more robust.
Why it works
The rotation set in the 3D view is applied to every vertex around the mesh centre; landmarks are rotated identically. Shape and size do not change.
How to check the result
- Check that landmarks still sit on the surface after rotation.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| filename | string | required | Scan stem. |
| edit | string | null | null | Source PLY basename. |
| rotation | object | required | Babylon rotation payload; rotation._m must contain keys 0,1,2,4,5,6,8,9,10 forming a 3×3 matrix. |
Algorithm / details
1) Extract 3×3 matrix from rotation._m. 2) Map to raw mesh basis: R_raw = diag(1,−1,−1) · R_babylon · diag(1,−1,−1). 3) Rotate vertices: v' = (v − c) @ R_raw + c. 4) Apply same transform to landmarks. 5) Write _rotated.ply and store rotation matrices in edit metadata.
v' = (v - c) R + c
v: vertex; c: rotation_center from quick_mesh_properties; R: raw rotation matrix.
Related: mesh-edit-mixin, apply-mesh-crop
Apply Mesh Cleanup
Keywords: cleanup, connected components, islands, disconnected mesh, POST apply-mesh-cleanup
Summary
Removes disconnected mesh islands from a preserved PLY, keeping the largest surface-area component by default or all components above a minimum area fraction. Writes _cleaned.ply when components are removed; returns skipped status when the mesh is already a single component or cleanup preconditions fail (e.g. post-elastic edits).
Considerations
Why use it
Removes small disconnected pieces from a preserved mesh, so stray fragments do not disturb alignment, landmarking or measurements.
Use it when
- The mesh contains floating fragments, dust or support remnants.
Skip it when
- The specimen is made of several real separate parts and you have not switched to the minimum-area option.
Why it works
The mesh is split into connected pieces scored by surface area. By default only the largest piece is kept; with the option on, every piece above a percentage of the total area is kept. Landmarks on removed pieces are dropped.
Signs of poor parameters
- Largest-only mode on multi-part specimens: real parts are removed.
Good parameters
- Minimum-area mode with a small percentage (0.5–2 %) when real separate parts exist.
How to check the result
- Compare before and after and check that no landmarks were dropped unexpectedly.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| filename | string | required | Scan stem. |
| edit | string | latest | Source PLY basename; defaults to latest edit. |
| use_island_volume_threshold | boolean | false | When true, keep every component whose surface area ≥ min_island_volume_percent of total area. |
| min_island_volume_percent | number | 1.0 | Minimum component area as percent of total surface area (clipped to 0.1–30). |
Algorithm / details
1) Build face adjacency graph via scipy connected_components on welded edges. 2) Score each component by surface area. 3) Select keep_indices (largest only, or all above threshold). 4) Drop faces from removed components, remove_unreferenced_vertices. 5) Drop landmarks whose nearest face was removed. 6) Save _cleaned.ply.
a_{\min} = (p/100)\, \sum_i a_i
a_min: area cutoff; p = min_island_volume_percent (default 1.0, clamped to 0.1–30); a_i: component surface areas; smaller islands are removed. The /100 converts percent to a fraction.
Related: mesh-edit-mixin, apply-mesh-shell
Apply Mesh Shell
Keywords: outer shell, get_outer_mesh, apply-mesh-shell, QuickMeshCanvas, refresh menu, complex volume, POST apply-mesh-shell
Summary
Quick mesh / HTTP entry that persists an outer-shell edit. Calls the shared Outer shell extraction algorithm (ALPACA.get_outer_mesh) — the same visibility utility used by Align to reference, landmark snap, mesh elastic, and other tools. UI: Quick mesh canvas cleanup/refresh menu → Outer shell.
Preserved meshes: get_outer_mesh on the PLY → _shell.ply. Voxel scans: marching cubes at threshold, outer-shell extraction, rasterize to a thickened volume mask (~10 voxel dilation), and save _shell.nii.gz plus lossy preview. Landmarks far from the shell surface are dropped.
Considerations
Why use it
Saves the outer shell of a mesh or volume as a new edit, so later steps work only on the outside surface.
Use it when
- You want every later step (alignment, landmarking, registration) to ignore inner surfaces for this subject.
Skip it when
- You study inner surfaces or cavities.
Why it works
The shared outer-shell extraction keeps only externally visible triangles. For a volume, the shell is turned back into a thick band of voxels (about 10 voxels deep) that keeps the original intensities, and everything else is cleared. Landmarks far from the shell are dropped.
Signs of poor parameters
- Using it on volumes when later steps need the interior (for example intensity-based registration of internal anatomy).
How to check the result
- Look at slices of the voxel result: the outer surface band should be intact and continuous.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| filename | string | required | Scan stem. |
| edit | string | null | null | Source edit basename. |
| gaussian_blur | number | null | null | Optional Gaussian sigma applied to voxel volume before marching cubes (voxel path only). |
Algorithm / details
1) Resolve source edit (preserved PLY or voxel NIfTI).
2) Run shared Outer shell extraction (ALPACA.get_outer_mesh) — see that Mesh Tools entry for the visibility algorithm.
3) Preserved mesh: trimesh cleanup → PreservedMeshEditWriter → _shell.ply.
4) Voxel: optional Gaussian blur → marching cubes → outer shell → rasterize shell to binary mask → dilate ~10 iterations → keep original intensities inside mask → write full-res and lossy NIfTI edits.
5) Drop landmarks far from the shell surface.
flowchart TD
U["Quick mesh Outer shell / POST apply-mesh-shell"] --> M{"preserved mesh?"}
M -->|"yes"| P["ALPACA.get_outer_mesh on PLY"]
M -->|"no"| V["MC + get_outer_mesh + rasterize"]
P --> S["Save _shell.ply"]
V --> T["Save _shell.nii.gz"]
S --> D["See Outer shell extraction for algorithm"]
T --> D
Related: alpaca-get-outer-mesh, apply-mesh-cleanup, mesh-elastic-registration, apply-mesh-snap-to-mesh, align-to-reference
Apply Mesh Snap to Mesh
Keywords: snap, landmarks, closest point, outer shell, POST apply-mesh-snap-to-mesh
Summary
Projects landmark positions onto the nearest point on the preserved-mesh surface (optionally the outer shell only), overwriting the scan's landmark JSON in place. Recomputes per-landmark distances and writes scan metadata statistics (mean/std, outlier counts vs voxel_size).
Considerations
Why use it
Moves a mesh subject's landmarks onto the nearest point of its surface, so landmarks that are slightly off measure the real surface.
Use it when
- After importing, transferring or hand-placing landmarks on a preserved mesh.
Skip it when
- Landmarks that are not meant to be on the surface.
Why it works
Each landmark moves to the closest point on the surface (or on its outer shell), and the snap distances are recorded.
Signs of poor parameters
- Outer shell on for landmarks on inner surfaces: they jump to the outside.
How to check the result
- Check the snap distances; large moves usually mark a misplaced landmark.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| filename | string | required | Scan stem. |
| edit | string | latest | PLY edit whose stem selects {stem}_landmarks.json. |
| use_outer_shell | boolean | true | When true, snap against Outer shell extraction (ALPACA.get_outer_mesh) of the loaded PLY. |
Algorithm / details
1) Load mesh (outer shell optional). 2) Read landmarks for edit stem. 3) alpaca.calculate_landmarks_closest_points moves each landmark to the nearest triangle surface point. 4) Write updated landmarks JSON and {stem}_landmark_distances.json with snap_distance array. 5) Update metadata.landmarks summary fields.
d_i = \|p_i - q_i\|_2
d_i: snap distance; p_i: source point; q_i: closest point on the target surface.
Related: alpaca-get-outer-mesh, mesh-edit-mixin, apply-mesh-shell
Elastic registration
Elastic registration (mesh pipeline)
Keywords: NR-ICP, nricp, amberg, preserved mesh, elastic, outer shell
Summary
Shared Non-Rigid ICP implementation used by MeshElasticRegistrationView (and landmark-transfer helpers). Mirrors the user goals of voxel ElasticRegistrationView — warp subjects toward the reference — but keeps fixed PLY topology instead of storing ANTs deformation fields. Prefer reading this entry for stage weights, preset tables, propagation math, and homology/mask internals; the HTTP view entry covers batch I/O and eligibility.
Considerations
Why use it
The non-rigid fitting engine for meshes: it bends each subject's surface onto the reference while keeping the subject's own vertices and triangles, so every vertex has a clear partner on the reference.
Use it when
- It runs behind mesh Elastic registration and mesh landmark transfer.
Why it works
Non-rigid ICP repeatedly matches each vertex to the nearest reference surface point and moves the mesh towards those matches, while a stiffness term keeps neighbouring vertices moving together. It runs in stages from stiff to flexible, so large shape differences are matched first and fine detail last. Dense meshes are simplified for the solve and the movement is carried back to every original vertex.
Signs of poor parameters
- Stages that become flexible too early (low stiffness first): the surface folds or slides into wrong matches.
- A correspondence distance that is too large: vertices match across gaps to the wrong structure. The distance is a fraction of the subject mesh's bounding-box diagonal (the presets use 8 % and 12 %), so it scales with specimen size.
- Too small: vertices find no match and do not move.
Good parameters
- Start from a preset (facial for single-sheet surfaces, complex mesh volume for closed shells such as skulls).
- Custom only after a preset nearly works: change one stage at a time.
- The preset distances (8 % for facial, 12 % for complex mesh volume); lower it when nearby structures attract wrong matches, raise it when parts of the surface do not move.
How to check the result
- Check the distance heatmap after registration, and look for folded or stretched triangles where the shapes differ most.
Inputs / options
| Name | Type | Default | Description |
|---|
| mode | string | custom | Preset: "facial", "complex_mesh_volume", or "custom". |
| mesh_elastic_custom_options.steps | number[3][] | [[0.01,0.5,10],…] | Custom mode only: up to 6 rows [stiffness, normal_weight, max_iter]. Landmark weight is always 0. |
| mesh_elastic_custom_options.gamma | number | 1.0 | Custom mode NR-ICP gamma (0.01–10). |
| mesh_elastic_custom_options.eps | number | 0.0001 | Custom mode convergence epsilon (1e-6–1). |
| distance_threshold | number | 0.10 | Custom mode correspondence cutoff as a fraction of the moving mesh's bounding-box diagonal (trimesh normalizes before NR-ICP); 0.01–0.5. Presets supply their own values. |
| use_vertex_normals | boolean | true | Enable normal terms in NR-ICP stages. |
| use_outer_shell | boolean | false | Extract outer shell on reference (and honor preset defaults). |
| outer_shell_only | boolean | false | Register decimated outer shells; upsample displacements back to full topology. |
| auto_extract_subject_outer_shell | boolean | false | Persist _shell.ply before NR-ICP; requires outer_shell_only=true. |
| mask_subject_with_template | boolean | false | Warp reference template to subject ROI and crop subject geometry (facial preset option). |
| mask_pad_centroid_fraction | number | 0.04 | Parallel-to-surface pad as a fraction of reference centroid size (0.005–0.20): max(|normal offset|, tangential exterior), a smooth along-surface ROI without the rounded isotropic rim tube. |
| tps_alpha_spacing_fraction | number | 0.25 | TPS regularization spacing fraction when TPS displacement propagation is used. |
Algorithm / details
Presets (MESH_ELASTIC_PRESETS):
• facial — five stages with decreasing stiffness ws≈0.045→0.009, wn mostly 0.5 then 0, distance_threshold 0.08 (8 % of the mesh diagonal); TPS upsample when decimated. Aimed at single-sheet facial surfaces.
• complex_mesh_volume — five stages, distance_threshold 0.12 (12 %), outer_shell_only + use_outer_shell; k-NN upsample (TPS on closed skull shells can spike). Nested marching-cubes sheets without a shell confuse correspondences.
• custom — user steps/gamma/eps plus request flags; defaults to a 4-stage schedule and 0.10 (10 %) cutoff.
Solver details: nricp_amberg with use_faces=False (memory-safe nearest vertex), wl forced to 0 (guidepoints only initialize via Procrustes; they are not soft constraints). Decimation cap MESH_ELASTIC_NRICP_VERTEX_CAP≈35000. After decimation, _sanitize_mesh_for_nricp merges vertices and drops disconnected debris that would singularize the LU factorization.
Homology/mask branch: temporarily sets homology_mode and runs NR-ICP reference→subject, then _crop_mesh_by_surface_distance with a parallel-to-surface pad from mask_pad_centroid_fraction (default 4% of centroid size; max of normal and tangential offset). Poor template fit yields holes — check template_fit_within_pad_fraction in logs/edit metadata.
Propagation: undecimated same-topology → direct; outer_shell_only → _nn_displacement_propagate_to_full_mesh (k=4 IDW); else → _tps_propagate_to_full_mesh with MESH_ELASTIC_TPS_ALPHA_SPACING_FRACTION=0.25. _assert_elastic_preserves_source_topology enforces vertex/face identity before save.
E = w_s E_{\mathrm{stiff}} + w_n E_{\mathrm{normal}} + E_{\mathrm{data}}
NR-ICP stage energy with landmark weight w_l = 0 in this mixin path; w_s, w_n from the stage tuple.
\mathrm{score} = 1 - \frac{1}{N}\sum_i \mathbb{I}(d_i > 6\Delta)
Surface agreement: d_i closest-point distance; Δ = voxel_size_mm; outliers beyond 6·Δ (fixed multiplier 6 in _mesh_elastic_error_threshold_mm).
t = \bar{y} - s R \bar{x}
Umeyama similarity init from guidepoints: R, s from SVD (reflection-fixed); t aligns centroids.
w_j \propto 1 / \max(\|p - c_j\|, \varepsilon)
IDW upsample weights from control points c_j to vertex p (default k_neighbors = 4); ε = 1e-10 distance floor.
flowchart TD
A["_resolve_mesh_elastic_settings"] --> B{"mode"}
B -->|"facial"| C["Sheet-friendly steps, dt=0.08mm, TPS"]
B -->|"complex_mesh_volume"| D["Shell-only steps, dt=0.12mm, k-NN"]
B -->|"custom"| E["User steps/gamma/eps + flags"]
C --> F["_run_mesh_nricp"]
D --> F
E --> F
F --> G["Optional Procrustes from guidepoints"]
G --> H["Decimate + sanitize"]
H --> I["Staged nricp_amberg"]
I --> J{"propagation"}
J -->|"direct"| K["Same topology"]
J -->|"knn"| L["Shell → full"]
J -->|"tps"| M["Decimated → full"]
K --> N["Topology assert + metrics"]
L --> N
M --> N
Related: mesh-elastic-registration, load-mesh-elastic-reference-surface, compute-mesh-to-reference-surface-distances
Elastic registration
Keywords: batch elastic, NR-ICP, preserved mesh, POST mesh-elastic-registration, WebSocket progress
Summary
Batch HTTP endpoint (POST /mesh-elastic-registration/) for Aurora’s mesh “Elastic registration” tool. It deforms every eligible preserved-mesh subject onto the reference surface with Non-Rigid ICP (trimesh nricp_amberg) while keeping fixed vertex topology — so per-vertex displacements, landmark transfer by index, and surface heatmaps remain well-defined.
This is the mesh half of the Elastic registration UI. Voxel projects instead call ElasticRegistrationView (ANTs SyN) on /elastic-registration/; mixed projects show both sections. Run after Align to reference: NR-ICP assumes subjects are already roughly co-located (rigid_prealignment is hard-coded off).
Presets: facial (single-sheet surfaces), complex_mesh_volume (outer-shell-only NR-ICP + k-NN upsample), or custom. Subjects already carrying an *_elastic.ply edit are skipped until that edit is removed.
Considerations
Why use it
Deforms every mesh subject onto the reference surface while keeping each subject's own vertices, so you get point-to-point correspondence, landmark transfer by vertex, and surface distance maps.
Use it when
- Mesh projects (surface scans, photogrammetry, exported PLYs) after Align to reference.
- You want dense correspondence or heatmaps between surfaces.
Skip it when
- Subjects are not rigidly aligned yet; the fitting assumes they are already roughly in place.
Effect on later steps
- Transfer landmarks (Helps): landmarks follow the vertex correspondence.
- Distance heatmaps (Helps): each vertex's distance to the reference shows where shapes differ.
Why it works
Each subject is fitted to the reference in stages, from stiff to flexible, with non-rigid ICP. Optionally the outer shells are used for the fit, the subject is first trimmed to the region covered by the reference, or guidepoints give a starting pose.
Signs of poor parameters
- The facial preset on closed shells (skulls): inner and outer sheets confuse the matches. Use complex mesh volume.
- Template masking with a poor template fit: the crop can cut holes in the subject.
- Running on subjects with very different coverage (one includes a region the other lacks) without cropping or masking.
Good parameters
- Facial for single open surfaces such as faces.
- Complex mesh volume (outer shell only) for closed or hollow structures.
- Template masking when subjects include extra regions the reference does not have.
Finding the best values
- Run one subject with the matching preset and look at its heatmap and shape.
- If fine detail is not matched, try custom with one extra flexible stage; if the surface folds, make the early stages stiffer.
- Apply the chosen settings to the cohort.
How to check the result
- Review the surface score and heatmap of every subject; outliers usually mean a poor rigid start or different coverage.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root. |
| reference | string | required | Reference scan stem or atlas. |
| mode | string | custom | facial | complex_mesh_volume | custom. UI always sends one of these (there is no backend balanced preset). |
| flagFilter | string | off | off | exclude | only for flagged subjects. |
| distance_threshold | number (fraction of mesh diagonal) | 0.10 custom; facial 0.08; complex 0.12 | NR-ICP correspondence cutoff. trimesh normalizes the moving mesh by its bounding-box diagonal before matching, so this is a fraction of that diagonal (UI slider shows it as %). |
| use_vertex_normals | boolean | true | Pass normals into nricp_amberg when enabled. |
| use_outer_shell / outer_shell_only | boolean | false (complex preset true) | Solve on outer shells; outer_shell_only keeps full input topology and upsamples via k-NN. |
| auto_extract_subject_outer_shell | boolean | false | Write a _shell.ply edit before NR-ICP; requires outer_shell_only. |
| mask_subject_with_template | boolean | false | Internal ref→subject NR-ICP + crop to a warped reference ROI before the final elastic pass. |
| mesh_elastic_custom_options | object | 4-stage default steps | Custom mode: steps [[ws,wn,max_iter],…], gamma, eps (wl is always forced to 0). |
Outputs
| Name | Type | Description |
|---|
| message | string | Mesh elastic registration complete for X/Y subject(s); optional warnings array. |
| {scan}_edit_{n}_elastic.ply | file | Topology-preserving elastically deformed mesh. |
| {scan}_edit_{n}_shell.ply / _masked.ply | file | Optional intermediate edits when auto-shell or template-mask branches run. |
| mesh_elastic_reference_ply | metadata + file | Persisted reference surface used for registration and later heatmaps. |
| elastic_to / elastic_surface_distance / elastic_error | JSON fields | Reference name; fraction of vertices within τ=6×voxel_size_mm; mean surface distance in mm. |
Algorithm / details
Goal: non-rigidly warp each preserved-mesh subject onto the reference surface without changing vertex count or face connectivity.
0) Resolve mode → settings (_resolve_mesh_elastic_settings). facial / complex_mesh_volume load preset steps and distance_threshold; custom merges mesh_elastic_custom_options. Reject auto_extract without outer_shell_only. Partition preserved-mesh scans; drop reference, faulty, existing *_elastic.ply, then flagFilter.
1) Reference surface (once): load preserved PLY in registration-frame shared mm, or marching-cubes a voxel reference (axis swap, σ=1, threshold). Optionally extract an outer shell. Persist mesh_elastic_reference_ply on the reference JSON.
2) Per subject — optional pre-branches.
• Outer-shell auto-extract: write _shell.ply and filter landmarks to the shell.
• Template mask: internal homology NR-ICP (reference → subject), crop by parallel-to-surface pad max(|n·offset|, tangential) = mask_pad_centroid_fraction × centroid_size, save _masked.ply.
• Guidepoints (≥3 pairs): Umeyama/Procrustes similarity init only (not soft NR-ICP landmarks; wl=0).
3) Final NR-ICP (subject → reference).
Choose solver mesh: full input, use_outer_shell on both, or outer_shell_only (shell for solve, full topology for output). Decimate toward ~35k verts; sanitize (merge vertices, drop tiny components) to avoid singular LU in nricp_amberg. Run multi-stage Amberg NR-ICP with steps [ws, wl=0, wn, max_iter], distance_threshold (fraction of the moving mesh diagonal), use_faces=False (cKDTree nearest vertex).
4) Upsample displacements back to full topology: direct copy if undecimated; k-NN IDW (k=4) when outer_shell_only; else thin-plate spline with α = 0.25 × median control spacing. Assert topology unchanged; save *_elastic.ply.
5) Metrics: unsigned closest-point distances from deformed vertices to the reference surface; store elastic_surface_distance = 1 − fraction(d_i > 6×voxel_size_mm) and elastic_error = mean(d_i). Return 200 with success count, or 500 if every subject failed.
E = w_s E_{\mathrm{stiff}} + w_l E_{\mathrm{lm}} + w_n E_{\mathrm{n}} + E_{\mathrm{data}}
NR-ICP energy: stiffness, landmark, normal, and data terms; correspondences with d > distance_threshold get zero data weight (preset cutoffs: facial 0.08, complex_mesh_volume 0.12, custom default 0.10, as fractions of the moving mesh diagonal).
\tau = 6\Delta
τ = 6·Δ with fixed multiplier 6 from _mesh_elastic_error_threshold_mm; Δ = voxel_size_mm (mesh-reference spacing).
\mathrm{score} = 1 - \frac{1}{N}\sum_i \mathbb{I}(d_i > \tau)
elastic_surface_distance; elastic_error is the mean of d_i.
w_j \propto 1 / \max(\|p - c_j\|, \varepsilon)
k-NN / IDW upsample from deformed controls c_j (default k_neighbors = 4); ε = 1e-10 floor on distance in 1/max(∥p−c_j∥, ε).
\alpha = 0.25 \cdot \mathrm{median\, NN}(c)
α = tps_alpha_spacing_fraction · median NN spacing among controls; default fraction MESH_ELASTIC_TPS_ALPHA_SPACING_FRACTION = 0.25 (UI option tps_alpha_spacing_fraction).
flowchart TD
A["POST /mesh-elastic-registration/"] --> B["Resolve mode: facial / complex_mesh_volume / custom"]
B --> C["Filter preserved-mesh subjects"]
C --> D["Load + persist reference surface"]
D --> E["For each subject"]
E --> F{"auto outer shell?"}
F -->|"yes"| G["Save _shell.ply"]
F -->|"no"| H["Optional guidepoint Procrustes"]
G --> H
H --> I{"mask_subject_with_template?"}
I -->|"yes"| J["Internal NR-ICP ref→subject + crop → _masked.ply"]
I -->|"no"| K["Final NR-ICP subject→reference"]
J --> K
K --> L{"solver mesh"}
L -->|"outer_shell_only"| M["NR-ICP on shell"]
L -->|"use_outer_shell"| N["Shell on subject + reference"]
L -->|"full"| O["Full registration input"]
M --> P["Decimate ~35k + sanitize"]
N --> P
O --> P
P --> Q["Multi-stage nricp_amberg wl=0"]
Q --> R{"upsample"}
R -->|"direct"| S["Copy vertices"]
R -->|"outer_shell_only"| T["k-NN displacement"]
R -->|"decimated"| U["TPS to full mesh"]
S --> V["Assert topology; save _elastic.ply + metrics"]
T --> V
U --> V
V --> E
E --> W["200 with registered count"]
Related: mesh-elastic-registration-mixin, mesh-elastic-surface-metric, load-mesh-elastic-reference-surface, align-to-reference, elastic-registration, alpaca-get-outer-mesh
Load Mesh Elastic Reference Surface
Keywords: reference surface, mesh_elastic_reference.ply, marching cubes, registration heatmap
Summary
Loads the reference triangle mesh used for mesh-elastic metrics and registration heatmaps. Resolution order: mesh_elastic_reference_ply from JSON (written at registration), preserved-mesh mesh_file PLY, dedicated voxel reference PLY basename, or marching-cubes fallback from the latest full-res NIfTI. Preserved-mesh results are mapped into the registration frame via mesh_elastic_reference_in_registration_frame.
Considerations
Why use it
Finds the exact reference surface that mesh elastic registration used, so heatmaps and metrics compare against the same surface.
Use it when
- It runs automatically for mesh heatmaps and metrics.
Why it works
It prefers the reference surface saved at registration time, then the reference's own mesh, then builds one from the reference volume.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| reference | string | required | Reference scan stem or "atlas". |
Algorithm / details
1) Open reference JSON (atlas.json or {reference}.json). 2) Try mesh_elastic_reference_ply on disk. 3) Else try preserved mesh_file PLY. 4) Else try {reference}_mesh_elastic_reference.ply. 5) Else marching cubes latest NIfTI with Gaussian σ=1, threshold from metadata, axes 0↔2 swap, reversed face winding.
Related: prepare-heatmap-reference-mesh, compute-mesh-to-reference-surface-distances, mesh-elastic-registration
Prepare Heatmap Reference Mesh
Keywords: heatmap, decimation, vertex cap, cache, dense distances
Summary
Builds a display-budget reference mesh for per-vertex heatmap coloring on preserved-mesh subjects. The full-resolution mesh_elastic_reference.ply stays on disk for landmark propagation; this function decimates to vertex_cap (matching the subject display budget) and caches vertices/faces as NPZ beside the source PLY for fast reuse in MarchingCubesView dense distance computation.
Considerations
Why use it
Makes a lighter copy of the reference surface for heatmap colouring, so distance maps display quickly. The full surface is still used for landmark transfer.
Use it when
- It runs automatically the first time a heatmap is shown and is reused afterwards.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root directory. |
| reference | string | required | Elastic reference stem (elastic_to). |
| vertex_cap | integer | required | Maximum reference vertices for heatmap queries (same cap as subject display mesh). |
Algorithm / details
1) Resolve full reference via load path or marching-cubes fallback. 2) Return cached NPZ if vertex_cap and source mtime match. 3) Decimate full mesh to vertex_cap with _decimate_trimesh_to_vertex_cap. 4) Save {cache_stem}_heatmap_ref_v{cap}_vertices.npz and faces NPZ. 5) Return in-memory Trimesh for distance queries.
flowchart LR
Full["Full reference PLY"] --> Dec["Decimate to vertex_cap"]
Dec --> Cache["NPZ cache"]
Cache --> Q["Open3D closest-point queries"]
Related: compute-mesh-to-reference-surface-distances, load-mesh-elastic-reference-surface
Compute Mesh to Reference Surface Distances
Keywords: surface distance, Open3D, RaycastingScene, heatmap, closest point
Summary
Computes unsigned Euclidean distance from each query vertex to the nearest point on a reference triangle mesh using Open3D RaycastingScene (same approach as MarchingCubesView dense_distances for voxels). Used for preserved-mesh heatmap coloring, elastic surface metrics, and template-mask quality checks. Processes vertices in chunks for memory efficiency.
Considerations
Why use it
Measures, for every vertex, the distance to the nearest point on the reference surface. These distances drive the heatmaps and the registration quality scores.
Use it when
- It runs automatically for heatmaps, elastic quality scores and template-mask checks.
Why it works
For each vertex the closest point on the reference triangles is found, and the straight-line distance in millimetres is stored. Distances are unsigned (they show how far, not whether inside or outside).
How to check the result
- Remember heatmaps use a simplified reference: very small distances (below the simplification) are approximate.
Inputs / options
| Name | Type | Default | Description |
|---|
| query_vertices | np.ndarray (N, 3) | required | Subject vertex positions in registration mm. |
| reference_mesh | trimesh.Trimesh | required | Reference surface mesh (often decimated for heatmaps). |
| label | string | null | null | Optional log label for timing messages. |
| chunk_size | integer | 100000 | Number of query vertices per Open3D batch. |
Algorithm / details
1) Build Open3D TriangleMesh from reference vertices/faces. 2) Create RaycastingScene and add triangles. 3) For each chunk of query_vertices, call compute_closest_points. 4) Store ||query − closest||₂ per vertex.
d_i = \min_{q \in S} \|v_i - q\|_2
Unsigned closest-point distance from vertex v_i to reference surface S.
Related: prepare-heatmap-reference-mesh, mesh-elastic-surface-metric
Mesh Elastic Surface Metric
Keywords: elastic score, surface distance, error threshold, registration quality
Summary
Summarizes a distance array into the scalar scores stored on elastic subjects: fraction of vertices above threshold, elastic_surface_distance (1 minus that fraction), and mean distance in mm. Threshold is typically 6× reference voxel size, matching voxel ElasticRegistrationView surface scoring.
Considerations
Why use it
Turns the vertex distances into simple scores (fraction of the surface within tolerance, and mean distance) so you can compare registration quality between subjects.
Use it when
- Reviewing which subjects registered well and which need attention.
Why it works
The surface score is the fraction of vertices closer than the tolerance (usually six reference voxel sizes); the mean distance is reported in millimetres.
How to check the result
- Sort subjects by score and inspect the lowest ones by eye.
Inputs / options
| Name | Type | Default | Description |
|---|
| distances | np.ndarray | required | Per-vertex distances to reference surface in mm. |
| error_threshold_mm | number | required | Acceptance threshold in mm (usually 6 × voxel_size). |
Algorithm / details
above_fraction = count(d > threshold) / N; surface_score = 1 − above_fraction; mean_distance = mean(d). Empty input returns (0, 0, 0).
\mathrm{score} = 1 - \frac{1}{N}\sum_i \mathbb{I}(d_i > \tau)
Fraction of vertices within tolerance τ of the reference surface; τ = error_threshold_mm, typically 6·voxel_size_mm.
\overline{d} = \frac{1}{N}\sum_i d_i
Mean closest-point distance (elastic_error).
Related: compute-mesh-to-reference-surface-distances, mesh-elastic-registration
Rigid & Elastic Registration
Rigid Batch Alignment
Align to reference
Keywords: rigid alignment, reference scan, ALPACA, ANTs, GPU Rigid, FireANTs, guidepoints, DINO-Reg, batch, preserved mesh
Summary
Rigidly aligns every eligible moving scan in a project into the chosen reference’s coordinate frame (rotation + translation, with optional uniform scaling). This is Aurora’s Step 2 “Align to reference” tool — the geometric normalization step that should run before intensity matching and elastic registration so anatomy is co-located across subjects.
Five methods achieve the same goal by different evidence:
• ALPACA (default) — surface point-cloud matching (FPFH + RANSAC + ICP) on meshes extracted from voxels or on preserved PLY meshes; works for mixed mesh/voxel projects.
• ANTs rigid — intensity-based ANTs Affine registration on voxel volumes (projected to rotation+translation); optional affine_initializer spherical search.
• GPU Rigid — FireANTs multi-scale affine capture then rigid refine with local normalized cross-correlation on voxel volumes only; requires CUDA; default moments initialization.
• DINO-Reg — voxel-only, training-free rigid alignment inspired by Song et al. (IEEE TMI 2025; https://doi.org/10.1109/tmi.2025.3567247 / https://github.com/RPIDIAL/DINO-Reg): tri-planar DINOv3 features + joint PCA, semantic-cluster landmark Kabsch (variable K), coarse→fine cosine refine (optional weak intensity NCC; no Dice), then the shared scipy pad/rotate/embed warp; requires CUDA. Debug folder writes are disabled by default.
• Manual guidepoints — Kabsch/Procrustes fit from homologous guidepoint pairs on reference and subject (voxels and preserved meshes).
Eligible movers exclude the reference, faulty scans, subjects already ending in an *_aligned edit, and subjects that already completed elastic registration. Progress streams over the progress WebSocket.
When method=alpaca, Poisson sampling and FPFH → RANSAC → ICP are documented under ALPACA rigid alignment. Optional outer_surface prep uses the shared Outer shell extraction utility under Mesh Tools (also available from the Quick mesh Outer shell edit via Apply Mesh Shell).
Considerations
Why use it
Puts every subject in the same position and orientation as the reference (and optionally the same size), so the same anatomy sits in the same place in every scan. Elastic registration, atlases, transfers and comparisons all depend on this starting point.
Use it when
- Before elastic registration, to give every subject its initial position on the reference.
- Always after extraction and basic cleanup, for any cohort you want to compare.
Skip it when
- Subjects were scanned in a fixed, identical position and already overlap well (check the all-subjects overlay).
Effect on later steps
- Elastic registration (Helps): elastic registration only corrects local shape differences; it needs subjects already in place and on the reference grid.
- Mesh elastic registration (Helps): non-rigid ICP assumes subjects are already roughly in place.
- Invert Alignment Landmarks (Helps): the stored alignment lets landmarks go back to each subject's original coordinates.
Why it works
ALPACA matches the shapes of the two surfaces (feature matching, many random trials, then fine ICP), so it works on meshes and volumes alike and does not care about intensities. ANTs rigid and GPU Rigid match intensities in the volumes. DINO-Reg matches image features from a pretrained vision model. Manual guidepoints fit the pose from matching points you place. All of them then move the subject onto the reference's grid.
Signs of poor parameters
- Scaling on when size is part of what you study: size differences are removed and later measurements no longer reflect real size.
- Outer surface off on hollow or porous specimens with ALPACA: inner surfaces can drive the match.
- ANTs rigid on scans with very different intensities or backgrounds without Background Offset Correction first: the minimum–maximum rescaling then puts the two images on different scales.
- Nearest interpolation for intensity volumes: jagged edges.
Good parameters
- ALPACA with outer surface on as the default; it is robust to intensity differences and works for mixed projects.
- ANTs rigid when the surfaces are ambiguous (very symmetric shapes) but intensities are reliable and corrected.
- Manual guidepoints for the few subjects where automatic methods fail.
- Scaling only when you study shape independent of size.
Finding the best values
- Run the batch with ALPACA and outer surface on.
- Check the all-subjects overlay and the grid; flag subjects that look flipped or offset.
- Delete the aligned edit of those subjects and rerun them with another method or with guidepoints.
How to check the result
- Use the all-subjects overlay on the reference: it should look like one slightly blurred specimen.
- Compare a few subjects side by side with the reference in the same view.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root containing extracted/ (and atlas/ when reference is atlas). |
| reference | string | required | Reference scan stem, or atlas for the project atlas. |
| method | string | alpaca | Alignment evidence: alpaca | ants | gpu-rigid | manual-guidepoints | dino-reg (case-insensitive). UI labels: ALPACA*, ANTs rigid, GPU Rigid, Manual guidepoints, DINO-Reg. |
| scaling | boolean | false | If true, estimate a uniform scale so subject size matches the reference before/during the rigid fit (formula differs by method). |
| outer_surface | boolean | false (UI default true) | When true (ALPACA path), run shared Outer shell extraction (ALPACA.get_outer_mesh) on reference and subject surfaces before Poisson sampling / FPFH. Same utility as Apply Mesh Shell / Quick mesh Outer shell. |
| interpolation | string | linear | Volume warp order for ALPACA/guidepoints rotation: linear → order 1, nearest → order 0. |
| flagFilter | string | off | Batch subject filter: off | exclude | only (flagged scans). |
| propagate_linked | boolean | True | When true, linked children skipped by the batch inherit the main scan’s rigid transform; response may include linked_propagation. |
| ants_rigid_options | object | see normalize_ants_rigid_options | ANTs-only nested knobs: aff_metric, aff_iterations, aff_shrink_factors, aff_smoothing_sigmas, aff_sampling, aff_random_sampling_rate, grad_step, use_histogram_matching, singleprecision. |
| gpu_rigid_options | object | see normalize_gpu_rigid_options | GPU Rigid (FireANTs) nested knobs when method is gpu-rigid: initializer (moments | identity | cof), loss_type, scales, iterations, optimizer, optimizer_lr, cc_kernel_size, use_registration_mask, singleprecision. |
Outputs
| Name | Type | Description |
|---|
| message | string | HTTP 200 summary, e.g. Alignment complete using {method} method. |
| {scan}_edit_{n}_aligned.nii.gz / .ply | file | Per-subject aligned volume (voxel path) or preserved mesh (PLY path). |
| {scan}_lossy_edit_{n}_aligned.nii.gz | file | Optional lossy preview when lossy_compression is configured (voxel). |
| {scan}_edit_{n}_aligned_landmarks.json | file | Landmarks propagated through the rigid embedding (ALPACA / manual-guidepoints / DINO-Reg rotation matrix, or ANTs GenericAffine + centroid shift). |
| {scan}_edit_{n}_aligned.nii.mask.gz | file | Paired mask replayed with the same rigid chain when a source mask exists. |
| alignment_* metadata | JSON fields | alignment_to, alignment_method, alignment_rigid_provenance (method, scaling/outer_surface/interpolation flags; ANTs includes ants_rigid_options; GPU-rigid includes gpu_rigid_options; DINO-Reg may include dino_reg_result with source/probe_name/label/angle_deg/refined_cosine), alignment_scale, alignment_calculated_parameters (rotation matrix, padding, crop indices, translation — shared by ALPACA, manual guidepoints, DINO-Reg, ANTs, and GPU-rigid; legacy ANTs projects may still reference alignment_ants_affine.mat), voxel_size → reference spacing; ALPACA also writes alignment_score / alignment_error / alignment_median_nn. DINO-Reg debug folders are off by default. |
Algorithm / details
Goal: place every moving subject into the reference canvas so later elastic registration and morphometrics compare homologous anatomy.
0) Parse directory, reference, method, scaling, outer_surface, interpolation, flagFilter, propagate_linked, and ants_rigid_options. Build the mover list from extracted/, drop the reference, faulty scans, and elastic-completed subjects; apply flagFilter; filter out linked children (already-aligned mains may be kept when children still need catch-up). Partition movers into preserved PLY vs voxel. ANTs and DINO-Reg are rejected (400) if the reference is a preserved mesh or no voxel movers remain. DINO-Reg also requires CUDA and loads DINOv3 ViT-L/16 once per batch.
1) Load reference.
• Preserved PLY reference: load latest PLY edit; build an imaginary voxel prism (resolve_mesh_reference_prism) so voxel movers can embed into a shared mm canvas.
• Voxel reference: load latest NIfTI edit, Gaussian-smooth (σ=1), read threshold/spacing. For ALPACA/guidepoints, marching-cubes the reference surface (optional outer shell / decimation).
2) Per moving scan — preserved PLY path (ALPACA or manual-guidepoints only).
Re-express the subject in the reference prism when needed. ALPACA: align_landmarks_to_mesh → compose_source_to_target_transform → apply_rigid_transform on vertices (and guidepoints). Manual: Kabsch from homologous guidepoints with optional radial scale. Save *_aligned.ply and update mesh metadata; when the reference is voxel-based, apply the X↔Z display-basis swap for Babylon convention.
3) Per moving scan — voxel NIfTI path (all four methods).
Common prep: load latest edit, estimate background below threshold, resample to reference_voxel_size via zoom_factor = subject_vs / reference_vs.
• ALPACA branch: marching-cubes the subject; optional outer shell and scale_match from mean radii; FPFH + multi-attempt RANSAC with burn-in scoring, then point-to-plane ICP (fallback to RANSAC if ICP worsens the score). Convert the rigid/similarity transform to voxel units; pad → rotate (ndimage.affine_transform with Rᵀ) → translate; embed by centroid into the reference-shaped canvas; strip a 20-voxel border to reduce later elastic edge artifacts; store_alignment_embedding for landmarks.
• ANTs branch (voxel only): optional mesh-radius scaling; integer centroid pre-shift of binary masks; min_max_normalize to [−1,1]; ants.registration Rigid at half resolution with ants_rigid_options; ants.apply_transforms at full resolution; restore_range; border strip. Landmark transform for ANTs is not implemented yet.
• Manual-guidepoints branch: require matching-shape guidepoint JSON on reference and subject; optional guidepoint radial scaling; Kabsch R; pad-rotate-translate; canvas embed + border strip.
• DINO-Reg branch (voxel only; Song et al. TMI 2025 inspired): normalize volumes; tri-planar DINOv3 encode + joint PCA; semantic-cluster landmark Kabsch (variable K); coarse→fine cosine Adam refine on axis-angle + residual translation (optional intensity NCC; no Dice); optional patch-NN pyramid fallback; project physical R,t through the shared scipy centroid-embed warp. Debug artefacts under dinoreg-rigid-debug/ are disabled by default; pose diagnostics are stored in alignment_rigid_provenance.dino_reg_result.
4) Save & propagate: write *_aligned.nii.gz with the reference affine; update alignment_* metadata; transform_landmarks_after_alignment for ALPACA/guidepoints/DINO-Reg; optional lossy NIfTI; replay the same chain on paired masks (nearest). Per-scan failures are logged and skipped so the batch can partially succeed.
5) Return 200 when the batch finishes; WebSocket reports Alignment complete for all scans.
z = \delta_{\mathrm{subj}} / \delta_{\mathrm{ref}}
Resample zoom: subject voxel size over reference voxel size.
s = \mathbb{E}\|p^{\mathrm{ref}} - \bar{p}^{\mathrm{ref}}\| / \mathbb{E}\|p^{\mathrm{subj}} - \bar{p}^{\mathrm{subj}}\|
Optional ALPACA scale from mean landmark radius of reference vs subject.
H = X_c^\top Y_c = U\Sigma V^\top,\quad R = V^\top C U^\top
Kabsch: cross-covariance of centered landmarks; C = diag(1,1,±1) fixes reflections.
v' = (v - \bar{x}) R + \bar{y}
Apply rigid map from subject centroid x̄ to reference centroid ȳ.
x_{\mathrm{out}} = R^\top x_{\mathrm{in}} + (c - R^\top c)
Volume rotation via scipy uses Rᵀ about padded center c.
flowchart TD
A["POST /align-to-reference/"] --> B["Parse method, scaling, outer_surface, interpolation, flagFilter, ants_rigid_options"]
B --> C["Build movers: drop reference, faulty, already aligned, elastic-done"]
C --> D["apply_flag_filter"]
D --> E{"method?"}
E -->|"ants / gpu-rigid / dino-reg"| F{"preserved-mesh reference or no voxel movers?"}
F -->|"yes"| G["400: voxel-only method"]
F -->|"no"| H["Continue"]
E -->|"alpaca / manual-guidepoints"| H
H --> I{"Load reference"}
I -->|"preserved PLY"| J["PLY + imaginary prism canvas"]
I -->|"voxel NIfTI"| K["Latest edit + optional marching cubes"]
J --> L["For each moving scan"]
K --> L
L --> M{"Moving type?"}
M -->|"preserved PLY"| N{"method?"}
N -->|"alpaca"| O["ALPACA mesh rigid; save *_aligned.ply"]
N -->|"manual-guidepoints"| P["Kabsch on guidepoints; save *_aligned.ply"]
N -->|"ants / gpu-rigid / dino-reg"| SKIP1["Skip preserved mesh"]
M -->|"voxel NIfTI"| Q["Resample to reference spacing; estimate background"]
Q --> R{"method?"}
R -->|"alpaca"| S["MC surface; FPFH/RANSAC/ICP; pad-rotate-translate; embed canvas"]
R -->|"ants"| T["Centroid shift; normalize; ANTs Rigid half-res; apply full-res"]
R -->|"gpu-rigid"| GR["Centroid shift; FireANTs Rigid multi-scale CC; GenericAffine .mat"]
R -->|"manual-guidepoints"| U{"Matching guidepoints?"}
U -->|"no"| SKIP2["Skip scan"]
U -->|"yes"| V["Optional scale; Kabsch; pad-rotate-translate; embed"]
R -->|"dino-reg"| DR["tri-planar DINOv3 + PCA; semantic landmarks; cosine refine; scipy embed"]
S --> W["Metadata + landmarks (ALPACA/GP/DINO-Reg) + optional lossy/mask"]
T --> W
GR --> W
V --> W
DR --> W
O --> X["Next scan"]
P --> X
SKIP1 --> X
SKIP2 --> X
W --> X
X --> L
L --> Y["200: Alignment complete"]
Related: alpaca-align-landmarks-to-mesh, mesh-elastic-registration, invert-alignment-landmarks, export-inverted-landmarks, registration-tools-overview, elastic-registration, alpaca-get-outer-mesh, alpaca-create-landmarks-from-mesh, normalize-ants-rigid-options
ALPACA rigid alignment
Poisson-disk sampling
Keywords: Poisson disk, pseudo-landmarks, point cloud, ALPACA, FPFH, ALIGNMENT_REFERENCE_POISSON_POINTS, Open3D
Summary
ALPACA stage that builds evenly spaced surface point clouds for FPFH feature matching. The reference always uses ALIGNMENT_REFERENCE_POISSON_POINTS = 5000 samples; the subject uses a vertex-ratio-proportional count so neighborhood statistics stay comparable on both preserved meshes and voxel-derived marching-cubes shells.
Considerations
Why use it
Turns each surface into an evenly spread cloud of points for ALPACA alignment. Even spacing means the shape descriptors are computed the same way on every subject.
Use it when
- It runs automatically inside ALPACA alignment.
Why it works
The reference is sampled with 5,000 points spread evenly (Poisson-disk sampling); the subject gets a number of points in proportion to its vertex count, so point density is comparable.
How to check the result
- If ALPACA struggles on very small or very large subjects, the vertex-count ratio may be unusual; compare the meshes' resolutions.
Inputs / options
| Name | Type | Default | Description |
|---|
| vertices | np.ndarray (N, 3) | required | Mesh vertex coordinates in world space. |
| faces | np.ndarray (M, 3) | required | Triangle indices into vertices. |
| n_landmarks | int | 0 | Number of landmarks to sample. When 0, uses ALIGNMENT_REFERENCE_POISSON_POINTS so density matches the alignment reference path. |
Algorithm / details
1) Convert vertices/faces to an Open3D TriangleMesh and compute vertex normals.
2) If n_landmarks is 0, use ALIGNMENT_REFERENCE_POISSON_POINTS = 5000.
3) Inside the alignment pipeline the subject count is max(1, round(5000 × |V_subject| / |V_reference|)).
4) sample_points_poisson_disk returns approximately uniform, non-clustering points.
5) Alignment later builds low (1/5) / medium (1/3) / full resolution tiers from these clouds for coarse-to-fine ICP.
n_{\mathrm{ref}} = 5000
Fixed reference Poisson budget (ALIGNMENT_REFERENCE_POISSON_POINTS).
n_{\mathrm{subj}} = \max\!\bigl(1,\,\mathrm{round}(5000\cdot |V_{\mathrm{subj}}|/|V_{\mathrm{ref}}|)\bigr)
Subject sample count scales with mesh vertex ratio so point density (and FPFH support) stays matched.
flowchart TD
A["Mesh verts/faces"] --> B["Open3D TriangleMesh + normals"]
B --> C{"n_landmarks?"}
C -->|"0 / default"| D["n_ref = 5000"]
C -->|"set"| E["Use requested n"]
D --> F["Poisson-disk sample"]
E --> F
F --> G["Point cloud for FPFH / ICP"]
Related: alpaca-get-outer-mesh, alpaca-align-landmarks-to-mesh, align-to-reference
ALPACA rigid alignment (FPFH · RANSAC · ICP)
Keywords: ALPACA, FPFH, RANSAC, ICP, rigid alignment, point cloud, Otsu, stochastic, symmetry, Porto, scan-to-reference
Summary
Aurora’s primary surface-based rigid alignment: an adaptation of ALPACA (Porto et al., 2021) that keeps only the rigid stages — FPFH matching, a stochastic multi-candidate RANSAC pool, symmetry disambiguation, and point-to-plane ICP. Landmark transfer via Coherent Point Drift from the original ALPACA is not used; homology for landmarks comes later from elastic voxel fields. Align to reference calls this path when method=alpaca (default). Optional outer_surface prep uses the shared Outer shell extraction utility under Mesh Tools, not an ALPACA-only stage.
Considerations
Why use it
Aligns two surfaces by shape alone, without any landmarks and without relying on intensities, which makes it robust across scanners, modalities and mixed mesh and volume projects.
Use it when
- It runs inside Align to reference when ALPACA is selected (the default).
Skip it when
- The shapes are nearly symmetric in several directions or very featureless; matching can then pick a rotated pose. Use guidepoints for those subjects.
Why it works
Both surfaces are sampled into point clouds and described by local shape features. Matching features propose many candidate poses (40 randomised trials); the best is chosen by how well the clouds overlap, parts that only exist in one shape are ignored, 180° flips are tested, and point-to-plane ICP refines the result (kept only if it improves the score).
Signs of poor parameters
- Specimens with large missing or extra regions (different cropping): the overlap score can favour a wrong pose.
- Inner surfaces left in place (outer surface off) on hollow specimens.
Good parameters
- Outer surface on, cleaned meshes, and similar cropping across subjects.
How to check the result
- Check flips first: look for subjects that are upside-down or mirrored relative to the reference in the overlay.
Inputs / options
| Name | Type | Default | Description |
|---|
| source_vertices / source_faces | np.ndarray | required | Reference mesh geometry that will be moved. |
| target_vertices / target_faces | np.ndarray | required | Subject mesh geometry that stays fixed. |
| target_metadata_path | str | required | Path to the subject's metadata JSON where alignment success/parameters are recorded. |
| scaling_mode | bool | False | If true, allow a similarity (rigid + uniform scale) fit instead of pure rigid. |
| outer_surface | bool | False | When true, preprocess reference and subject with shared Outer shell extraction (ALPACA.get_outer_mesh / Mesh Tools) before Poisson sampling. |
Outputs
| Name | Type | Description |
|---|
| transformation_matrix | np.ndarray (4x4) | Rigid/similarity transform returned by the alignment (rotation + RMS-descaled residual translation; see algorithm notes). |
| source_centroid | np.ndarray (3,) | Centroid of the source cloud used during normalization. |
| target_centroid | np.ndarray (3,) | Centroid of the target cloud used during normalization. |
| scale | float | Scale factor when scaling_mode is enabled; identity scale otherwise. |
| chosen_transform | np.ndarray (4x4) | Internal normalized-space transform selected by the pipeline before world remapping. |
| normalization_scale_factor | float | Shared RMS normalization scale applied to both clouds. |
| target metadata (side effect) | JSON file | When update_alignment_metadata is true, alignment success/parameters are written to target_metadata_path. |
Algorithm / details
Aurora adopts ALPACA’s rigid pipeline only (no CPD landmark transfer).
1) Optional outer shell — if outer_surface, call the shared Outer shell extraction utility (Mesh Tools / ALPACA.get_outer_mesh; same as Apply Mesh Shell) on reference and subject.
2) Poisson-disk clouds — reference n_ref = 5000; subject scaled by vertex ratio (see Poisson-disk sampling). Optional scaling_mode equalizes mean centroid distances first.
3) Center + RMS-normalize — subtract centroids; divide by the reference cloud’s RMS radius so distance thresholds are scale-agnostic.
4) Geometry-adaptive FPFH — map surface smoothness (median normal angular spread) linearly to a feature radius between 8% and 15% of the bounding-box diagonal; compute FPFH descriptors; keep bidirectional matches (unless too few), then edge-length-validate correspondences.
5) Stochastic RANSAC pool — run n_ransac_attempts = 40 candidates, each with independent parameter jitter. Record per-reference-point NN distances → an N×40 matrix.
6) Otsu selection — threshold all pooled distances (Otsu) into peak vs tail; rank candidates by composite score = median × (1 + IQR) / normal_consistency (lower is better).
7) Homology mask — exclude reference points that fall in the Otsu tail for ≥90% of the top-10 ranked tries (crop edges / non-overlap).
8) Symmetry check — test four 180° axis flips; keep the one maximizing normal consistency.
9) Point-to-plane ICP — refine on the masked cloud (coarse→medium→full tiers). Discard ICP if the post-ICP composite score is worse than pre-ICP (partial-overlap safeguard).
10) Map the 4×4 transform back to physical coordinates (undo RMS scale + centroids); write subject metadata / return the subject→reference mapping for Align to reference to apply to voxels, masks, and landmarks.
r_{\mathrm{FPFH}} = \bigl(0.08 + 0.07\cdot s\bigr)\, L_{\mathrm{diag}}
Geometry-adaptive FPFH radius; s ∈ [0,1] from median normal angular spread (smoother → smaller radius); L_diag = reference cloud bounding-box diagonal. Code maps smoothness onto the 8%–15% band.
\mathrm{score} = \frac{\mathrm{median}(\mathbf{d})\cdot\bigl(1+\mathrm{IQR}(\mathbf{d})\bigr)}{\mathrm{normal\_consistency}}
Composite ranking over the 40 RANSAC candidates; d = per-point NN distances after that try; lower score is better (tight low distances + high normal agreement).
\tilde{X} = (X - c_s)/s,\quad \tilde{Y} = (Y - c_t)/s
Centroid-centered, RMS-normalized clouds; s = RMS radius of the reference cloud.
R'=R,\quad t' = s\, t + c_t - R\, c_s
Lift the normalized rigid (or similarity) transform back to physical coordinates.
ALPACA (Porto et al., 2021) — Literature — Porto et al., 2021: Original SlicerMorph ALPACA; Aurora keeps rigid stages only (no CPD landmark transfer). (https://doi.org/10.1111/2041-210X.13689)
FPFH descriptors — Literature — Rusu et al., 2009: Fast Point Feature Histograms used for correspondence before RANSAC. (https://doi.org/10.1109/ROBOT.2009.5152473)
Open3D RANSAC / ICP — Open3D — open3d.pipelines.registration: Feature matching, RANSAC pose, and point-to-plane ICP refinement backends. (https://www.open3d.org/docs/latest/tutorial/Advanced/global_registration.html)
flowchart TD
A["Reference + subject meshes"] --> B{"outer_surface?"}
B -->|yes| C["Outer shell extraction (Mesh Tools)"]
B -->|no| D["Full meshes"]
C --> E["Poisson-disk sampling"]
D --> E
E --> F["Center + RMS normalize"]
F --> G["Adaptive FPFH + match filter"]
G --> H["40× jittered RANSAC"]
H --> I["Otsu score + homology mask"]
I --> J["180° symmetry pick"]
J --> K["Point-to-plane ICP"]
K --> L{"ICP improved score?"}
L -->|yes| M["Keep ICP pose"]
L -->|no| N["Keep pre-ICP pose"]
M --> O["Physical 4×4 + metadata"]
N --> O
Related: align-to-reference, alpaca-get-outer-mesh, alpaca-create-landmarks-from-mesh, normalize-ants-rigid-options, invert-alignment-landmarks
ANTs rigid options
Normalize ANTs Rigid Options
Keywords: ANTs, rigid registration, aff_metric, iterations, request parsing
Summary
Parses and validates the ants_rigid_options object sent with Align to Reference when method is ants. Invalid or missing fields fall back to safe defaults and are clipped to allowed ranges so the UI can expose advanced ANTs tuning without risking unstable registration parameters.
Considerations
Why use it
Checks and limits the advanced settings for ANTs rigid alignment, so a typo or extreme value cannot make the registration unstable.
Use it when
- It runs automatically when you use ANTs rigid alignment.
Why it works
Each setting is checked against the allowed values and clipped to a safe range; missing or invalid values fall back to the defaults (Mattes metric, 4 resolution levels).
Signs of poor parameters
- Mean squares as the metric on scans whose intensities were not corrected and normalized: it assumes identical intensities.
- Removing the coarse levels (shrink factors) for subjects that start far apart: the registration can get stuck.
Good parameters
- The defaults; change the metric only with a reason (GC for strong local contrast, Mean squares only after intensity normalization).
Inputs / options
| Name | Type | Default | Description |
|---|
| request_data | dict | required | Full POST body; reads request_data['ants_rigid_options'] when present. |
| ants_rigid_options.aff_metric | str | mattes | ANTs affine metric; allowed: mattes, GC, meansquares. |
| ants_rigid_options.aff_iterations | list[int] | [1000, 800, 600, 100] | Per-level iteration counts; each value clipped to 0–5000. |
| ants_rigid_options.aff_shrink_factors | list[int] | [6, 4, 2, 1] | Multi-resolution shrink factors; each value clipped to 1–16. |
| ants_rigid_options.aff_smoothing_sigmas | list[int] | [3, 2, 1, 0] | Gaussian smoothing sigmas per level; each value clipped to 0–8. |
| ants_rigid_options.aff_sampling | int | 48 | Metric sampling percentage; clipped to 8–256. |
| ants_rigid_options.aff_random_sampling_rate | float | 1.0 | Random subsampling rate for metric evaluation; clipped to 0.01–1.0. |
| ants_rigid_options.grad_step | float | 0.15 | Gradient step size; clipped to 0.001–1.0. |
| ants_rigid_options.use_histogram_matching | bool | true | Enable ANTs histogram matching between fixed and moving images. |
| ants_rigid_options.singleprecision | bool | true | Run ANTs registration and apply_transforms in single precision. |
Algorithm / details
Extract ants_rigid_options from the request dict (or use empty dict). Validate aff_metric against an allow-list. Clip each numeric field to documented min/max bounds; replace invalid tuples with hard-coded defaults. Return a normalized dict consumed directly by ants.registration and ants.apply_transforms in AlignToReferenceView.
flowchart TD
A["request_data"] --> B{"ants_rigid_options dict?"}
B -->|"no"| C["Use empty dict"]
B -->|"yes"| D["Read each field"]
C --> E["Clip and default"]
D --> E
E --> F["Return normalized opts"]
Related: align-to-reference, registration-tools-min-max-normalize, registration-tools-restore-range
Voxel Elastic Registration
Elastic registration (voxel / ANTs SyN)
Keywords: elastic, SyN, ANTs, non-rigid, deformation field, voxel, registration
Summary
Voxel half of Aurora’s Elastic registration UI (POST /elastic-registration/). After rigid alignment has placed subjects on the reference grid, this endpoint runs ANTs SyNOnly non-linear registration so each moving NIfTI deforms onto the reference intensity/geometry. Unlike mesh NR-ICP, it stores forward/inverse deformation fields and reports Dice, NCC, and intensity error in addition to the shared surface score.
Use this for volumetric CT/MRI subjects. Preserved PLY subjects are excluded here and handled by MeshElasticRegistrationView. Mixed projects expose both tools under the same Elastic registration menu.
Considerations
Why use it
Deforms each subject's volume onto the reference so that every point of anatomy has a matching point in every subject. The resulting deformation fields are what let you transfer landmarks and masks, build atlases and map shape differences.
Use it when
- After Align to reference, for volumetric CT, micro-CT and MRI cohorts.
- Before transferring landmarks or masks, or building an atlas.
Skip it when
- Subjects are not yet rigidly aligned on the reference grid; registration refuses subjects with a different grid size.
- Mesh-only projects; those use mesh elastic registration.
Effect on later steps
- Transfer landmarks (Helps): landmarks follow the deformation fields.
- Transfer masks (Helps): masks follow the same fields.
- Create atlas (Helps): the atlas averages these fields and intensities.
Why it works
Each image is rescaled to [-1, 1] by its own minimum and maximum, then ANTs SyN finds a smooth, invertible deformation that makes the subject look like the reference according to the chosen similarity measure, working from coarse to fine resolution. Optional steps add an affine start, a second refining pass, masks that limit where similarity is measured, or distance maps instead of intensities. Quality is reported as overlap (Dice), surface agreement, correlation and intensity error.
Signs of poor parameters
- Mean squares or Demons on scans that were not background-corrected and intensity-normalized: they assume the same tissue has the same value.
- Very flexible settings (high gradient step, low smoothing) on noisy data: the deformation follows noise and folds locally.
- Masks that cut the structure: registration has no information there and the field is extrapolated.
- Skipping Background Offset Correction when scans contain dark outliers: the min–max rescaling then gives each image a different low end.
Good parameters
- CC (neighbourhood cross-correlation) for most CT, micro-CT and single-modality MRI; it ignores local brightness and contrast differences.
- Mattes when contrast differs between subjects (different scanners or staining).
- Demons or Mean squares only after Background Offset Correction and Intensity Normalization, when they can be fast and precise.
- A middle guided preset first; move up for accuracy only if the metrics and the heatmap show it is needed.
Finding the best values
- Run one representative subject with a middle preset of the metric that suits your data.
- Look at the heatmap and the overlay with the reference, and note Dice and surface score.
- If details are not matched, try a higher preset or double pass; if the result looks folded or noisy, go lower or smoother.
- Run the cohort with the chosen settings and review the metrics; rerun outliers individually.
How to check the result
- Sort subjects by Dice and surface score and inspect the lowest ones.
- Overlay the warped subject on the reference: edges should coincide with no visible tearing or folding.
- Check that transferred landmarks land on the same anatomy across very different subjects.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | string | required | Project root. |
| reference | string | required | Fixed image stem or atlas. Preserved-mesh references are temporarily voxelized for movers. |
| quick_registration | string | UI guided presets or custom | Preset key such as guided-{mattes|cc|demons}-{1-5}, legacy low-fast/mid/high, or custom. |
| affine_initialization | boolean | false | Optional affine stage whose displacement initializes SyN. |
| double_pass | boolean | false | Second SyN pass with refined parameters. |
| use_mask / prefer_mask / reference_mask_only | boolean | UI toggles; reference_mask_only default true | Restrict registration to mask support. use_mask is mutually exclusive with do_dice_adjustment in the UI. |
| mask_label | int | 1 | Label value treated as foreground when building fixed/moving masks under use_mask / prefer_mask. |
| do_dice_adjustment | boolean | false | Post-hoc threshold search (±1500) to improve Dice on overlapping foreground. |
| use_distance_map | boolean | false | Register Euclidean distance transforms instead of raw intensities. |
| flagFilter | string | off | off | exclude | only. |
| elastic_custom_options | object | see normalize_elastic_custom_options | Custom SyN knobs: reg_iterations, syn_metric (CC|mattes|demons|meansquares), syn_sampling, grad_step, flow_sigma, total_sigma, singleprecision. |
Outputs
| Name | Type | Description |
|---|
| {scan}_edit_{n}_elastic.nii.gz | file | Warped moving volume in reference space. |
| {scan}_edit_{n}_elastic_fwd.nii.gz / _inv.nii.gz | file | Forward and inverse deformation fields (plus .mat if an affine component exists). |
| lossy elastic preview | file | Downsampled preview when lossy_compression is configured. |
| elastic_to / elastic_dice / elastic_ncc / elastic_surface_distance / elastic_error | JSON fields | Reference name; Dice; normalized cross-correlation; shared surface-within-τ fraction; intensity MAE in overlap. |
Algorithm / details
Goal: non-linearly warp each voxel subject onto the reference volume after rigid alignment has matched grid shape and pose.
0) Load the fixed image (latest reference NIfTI, or build_temporary_mesh_reference_volume when the reference is a preserved mesh). Enumerate voxel movers; exclude reference, faulty scans, linked children, subjects already ending in *_elastic.nii.gz, and apply flagFilter. Movers whose array shape ≠ reference grid fail with 400 — re-run Align to reference first.
1) Per subject prep: optionally downsample if any axis > 512³ for the SyN solve; min_max_normalize to [−1,1]; optional EDT distance-map mode; optional masks (use_mask / prefer_mask / reference_mask_only).
2) Optional affine_initialization → displacement field used as SyN initial transform.
3) ants.registration with type_of_transform SyNOnly using the selected guided preset or elastic_custom_options (metric, iterations, sampling, sigmas, grad_step). Optional double_pass runs a second SyN with refined settings. If multiple .nii.gz transforms are produced, compose them into a single warp field.
4) ants.apply_transforms on the original-resolution moving image (and paired mask with nearestNeighbor). Optional do_dice_adjustment searches thresholds around the current metadata threshold to improve Dice.
5) Metrics & save: Dice on thresholded foreground; surface score with τ=6×voxel_size (same formula as mesh elastic); NCC on the union mask; intensity MAE as elastic_error. Write *_elastic.nii.gz, fwd/inv fields, lossy preview, update elastic_* metadata, stream progress. Batch continues on per-scan errors.
\mathrm{Dice} = \frac{2\,|A \cap B|}{|A| + |B|}
Overlap of thresholded registered vs reference foregrounds; optional ternary search of the subject threshold in a ±1500 intensity window maximizes Dice.
\mathrm{score} = 1 - \frac{1}{N}\sum_i \mathbb{I}(d_i > 6\Delta)
Fraction of outer-shell vertices whose bidirectional surface distance stays within six voxels; Δ = voxel_size; fixed multiplier 6 (paper §2.5 / _mesh_elastic_error_threshold_mm).
\mathrm{NCC} = \mathrm{corr}(I, J)\big|_{\mathrm{union\ mask}}
Normalized cross-correlation of intensities on the union of thresholded reference and registered-subject masks; reported on [−1, 1].
flowchart TD
A["POST /elastic-registration/"] --> B["Load fixed volume / temp mesh voxelization"]
B --> C["Filter voxel movers; require matching grid shape"]
C --> D["For each subject"]
D --> E["Optional downsample; normalize; optional EDT / masks"]
E --> F{"affine_initialization?"}
F -->|"yes"| G["Affine → displacement init"]
F -->|"no"| H["SyNOnly registration"]
G --> H
H --> I{"double_pass?"}
I -->|"yes"| J["Second SyN refine"]
I -->|"no"| K["Compose warp fields if needed"]
J --> K
K --> L["apply_transforms at full resolution"]
L --> M{"do_dice_adjustment?"}
M -->|"yes"| N["Threshold search for Dice"]
M -->|"no"| O["Dice, surface, NCC, MAE metrics"]
N --> O
O --> P["Save _elastic.nii.gz + fwd/inv fields + metadata"]
P --> D
D --> Q["200 batch complete"]
Related: align-to-reference, mesh-elastic-registration, registration-tools-overview, registration-tools-min-max-normalize, registration-tools-calculate-affine-field
Landmark Inversion & Export
Invert Alignment Landmarks
Keywords: landmarks, inverse transform, pre-alignment, morphometrics, multi-bone alignment
Summary
Inverts landmarks placed on rigidly aligned scans back into the shared pre-alignment coordinate space (physical mm at reference voxel spacing), writing them onto the pre-alignment edit/raw landmark slot (via resolve_pre_alignment_landmark_stem — raw {scan}_landmarks.json if the first aligned edit is 0, otherwise the edit before the first _aligned). Use this after rigid alignment when you need landmark coordinates comparable across subjects or across multiple bones aligned separately from the same scan.
Considerations
Why use it
Moves landmarks placed on aligned subjects back into each subject's original, pre-alignment coordinates, so coordinates can be compared with raw data or combined across bones aligned separately from the same scan.
Use it when
- You need landmark coordinates in the original scan space (for example to combine with other measurements from the raw scan).
- Several bones from the same scan were aligned separately (to different targets) and must be put back into one shared frame.
Skip it when
- You analyse shape in the aligned space only; the aligned coordinates are already comparable.
- Scans that were rotated, cropped or resampled by hand (or by any step other than rigid alignment) after extraction: the inversion only undoes the rigid alignment, so the result would be wrong. A real risk; check each scan's edit history first.
Why it works
Each subject's stored rigid alignment (rotation, translation, padding, optional scale) is undone step by step on its landmarks, and the result is written to the edit before alignment. Subjects without a stored alignment or without valid landmarks are skipped, as are faulty scans, and Aurora asks before overwriting landmarks already on the pre-alignment edit.
Signs of poor parameters
- Running it after an aligned edit was deleted and redone without the stored alignment parameters: the inversion uses whatever is recorded in the metadata.
How to check the result
- Open a subject's pre-alignment edit: the inverted landmarks should sit on the same anatomy as in the aligned edit.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Project root directory. |
| scan_names | list[str] | null | Optional subset of subject names to process; all extracted subjects when omitted. |
| flagFilter | string | off | Batch subject filter: off | exclude | only. |
| overwriteExistingLandmarks | boolean | True | When false, skip subjects that already have landmarks on the pre-alignment edit/raw slot. |
Algorithm / details
1) Resolve subject list (explicit scan_names or all extracted folders), skipping faulty scans unless they are the reference. 2) For the reference subject, copy the latest edit landmarks unchanged. 3) For aligned subjects, load alignment_calculated_parameters from metadata and pick the latest landmark file from edits before any elastic edit. 4) Apply the inverse rigid pipeline: canvas mm → reference voxels, inverse rotation about the stored rotation center (also subtracting residual alignment_translation_voxel when present; older ALPACA projects may recover/persist missing T), subtract padding, undo scale_match, convert back to mm. 5) Write {scan}_{pre-alignment stem}_landmarks.json and return per-subject status.
v_{\mathrm{pad}} = R^\top (v_{\mathrm{c}} - c_{\mathrm{c}} - T) + c_{\mathrm{rot}}
Undo canvas rotation and residual translation: v_c canvas voxel; c_c canvas centroid; T alignment_translation_voxel (ALPACA; recovered if missing); c_rot original rotation center.
v_{\mathrm{pre}} = (v_{\mathrm{pad}} - p_{\mathrm{low}}) / s
Remove padding_low and undo scale_match s.
x_{\mathrm{mm}} = v_{\mathrm{pre}} \cdot \delta_{\mathrm{ref}}
Convert pre-scale voxels to millimeters with reference voxel size.
flowchart TD
A["POST directory"] --> B["Resolve subjects"]
B --> C{"reference subject?"}
C -->|"yes"| D["Copy latest edit landmarks"]
C -->|"no"| E["Load alignment_calculated_parameters"]
E --> F["Pick pre-elastic landmark edit"]
F --> G["Inverse rotation padding scale"]
D --> H["Write pre_alignment landmarks JSON"]
G --> H
Related: align-to-reference, export-inverted-landmarks
Export Inverted Landmarks
Keywords: CSV export, landmarks, pre-alignment, morphometrics, spreadsheet
Summary
Collects pre-alignment landmark files (the same stem Invert Alignment Landmarks writes via resolve_pre_alignment_landmark_stem) from every non-faulty subject and returns them as a downloadable CSV. Run this after Invert Alignment Landmarks when you need landmark coordinates in R, Python, or external morphometrics tools — each row is one subject with landmark_x/y/z columns.
Considerations
Why use it
Exports everyone's landmarks in their original, pre-alignment coordinates as one table.
Use it when
- After Invert Alignment Landmarks, when analysis needs original scan coordinates.
How to check the result
- Check the row count equals the number of non-faulty subjects and spot-check one subject's coordinates against the viewer.
Inputs / options
| Name | Type | Default | Description |
|---|
| directory | str | required | Project root directory containing extracted/. |
Algorithm / details
1) List all subject folders under extracted/, skipping faulty scans. 2) For each subject, load landmarks from the stem returned by resolve_pre_alignment_landmark_stem (same pre-alignment edit/raw slot invert writes to). 3) Flatten landmark positions into columns (landmark_i_x/y/z; semi_ prefix for semi-automatic landmarks). 4) Build a pandas DataFrame sorted by subject name, write a timestamped CSV to .temp/, and stream it as a FileResponse attachment.
flowchart TD
A["POST directory + output_suffix"] --> B["Scan extracted subjects"]
B --> C["Load inverted landmark JSON"]
C --> D["Flatten to x y z columns"]
D --> E["Build DataFrame"]
E --> F["Write CSV and download"]
Related: invert-alignment-landmarks
Registration Tools
RegistrationTools
Keywords: registration utilities, ANTs, normalization, lossy compression, landmarks, background
Summary
Shared helper class used across rigid alignment, elastic registration, rotation/crop views, preprocessing, and mask pipelines. It centralizes intensity normalization for ANTs, background estimation, lossy NIfTI preview generation, affine-to-displacement-field conversion for elastic initialization, and landmark coordinate propagation through geometric edits.
Considerations
Why use it
A shared toolbox used by alignment, elastic registration, rotation, cropping and masks, so all of them estimate background, rescale intensities, save previews and move landmarks the same way.
Use it when
- It is used automatically by those tools; you do not run it directly.
Why it works
Typical sequence: estimate the background, rescale to [-1, 1] for ANTs, register, restore the original intensity range, save a preview and update landmark files.
Algorithm / details
RegistrationTools is a stateless utility class instantiated where needed (e.g. AlignToReferenceView.registration_tools). Methods are composed in registration workflows: estimate background → normalize to [-1,1] for ANTs → register → restore original intensity range → save lossy previews and transform landmark sidecars.
flowchart TD
A["Volume or landmarks"] --> B["Background estimation"]
B --> C["min_max_normalize"]
C --> D["ANTs registration"]
D --> E["restore_range"]
E --> F["save_as_lossy_nifti + landmark transforms"]
Related: align-to-reference, registration-tools-calculate-affine-field, registration-tools-save-lossy-nifti
Get Background Value
Keywords: background, histogram, intensity, border pixels, segmentation threshold
Summary
Estimates the tissue-background intensity level from a 3D volume. Used before padding, resampling, and ANTs normalization so empty regions are filled with a statistically consistent background rather than zero or an arbitrary constant.
Considerations
Why use it
Estimates a scan's background level so that padding added by rotation, cropping or alignment is filled with a value that looks like the real background instead of zero.
Use it when
- It runs automatically when tools pad or resample volumes.
Effect on later steps
- Registration (Helps): padding that matches the background does not create artificial edges for the similarity metric.
Why it works
It smooths the volume, finds the main histogram peaks and takes the lower of the two largest as the background (or, in border mode, the most common value of the outer two-voxel shell).
How to check the result
- If padded borders look brighter or darker than the background, the scan has an unusual histogram; Background Offset Correction first usually fixes it.
Inputs / options
| Name | Type | Default | Description |
|---|
| image_data | np.ndarray | required | 3D volume array. |
| mode | str | peak-no-zero | Estimation strategy: peak-no-zero (Gaussian-smoothed histogram peaks) or border (2-voxel shell histogram). |
| threshold | number | null | Optional intensity cutoff for border mode; voxels above threshold are excluded from the border shell. |
Algorithm / details
peak-no-zero: Gaussian smooth (sigma=3), find histogram peaks, pick the lowest-intensity among the two highest-frequency peaks, return the mean intensity in that bin. border: Gaussian smooth (sigma=1), collect 2-voxel-thick border voxels (optionally masked by threshold), return histogram mode or mean when variance is low.
flowchart TD
A["image_data"] --> B{"mode"}
B -->|"peak-no-zero"| C["Smooth histogram peaks"]
B -->|"border"| D["Sample 2px border shell"]
C --> E["Pick background intensity"]
D --> E
Related: registration-tools-overview, registration-tools-get-adjusted-background-value
Get Adjusted Background Value
Keywords: background, normalization, linear mapping, ANTs
Summary
Maps a background intensity from the original value range into a new normalized range (typically [-1, 1] for ANTs). Use alongside min_max_normalize so padded and transformed regions keep the correct background level after intensity rescaling.
Considerations
Why use it
Converts a background value into the rescaled range used for registration, so padding stays at the right level after rescaling.
Use it when
- It runs automatically alongside the min–max rescaling.
Inputs / options
| Name | Type | Default | Description |
|---|
| background_value | number | required | Background intensity in the original image range. |
| image_data | np.ndarray | required | Source volume used to determine original min/max. |
| new_min | number | required | Target minimum of the normalized range. |
| new_max | number | required | Target maximum of the normalized range. |
Algorithm / details
Linearly map background_value from [min(image_data), max(image_data)] to [new_min, new_max] using the same scaling as min_max_normalize.
b' = \frac{b - m}{M - m}(M' - m') + m'
Map background b from [m, M] into the normalized display range [m', M'].
Related: registration-tools-min-max-normalize, registration-tools-restore-range
Min–Max Normalize
Keywords: normalization, intensity, ANTs, memory efficient
Summary
Linearly rescales voxel intensities to a target range while preserving relative contrast. This is the standard preprocessing step before ANTs rigid and elastic registration in Aurora, which expect numerically stable floating-point images (commonly [-1, 1]).
Considerations
Why use it
Rescales an image so its minimum and maximum map to a fixed range (usually [-1, 1]) before ANTs registration, which keeps the numbers stable.
Use it when
- It runs automatically before ANTs rigid and elastic registration.
Effect on later steps
- Background Offset Correction and Intensity Normalization (Helps): because this rescaling uses each image's own extremes, removing dark and bright outliers first gives fixed and moving images the same scale.
Why it works
Every value is shifted and scaled linearly so the smallest becomes the new minimum and the largest the new maximum. A single extreme voxel therefore sets the scale for the whole image.
How to check the result
- If registration of one subject behaves oddly, check its histogram for isolated extreme values.
Inputs / options
| Name | Type | Default | Description |
|---|
| image | np.ndarray | required | Input volume. |
| new_min | number | 0 | Target minimum after normalization. |
| new_max | number | 1 | Target maximum after normalization. |
Algorithm / details
Compute image min/max; if equal, return a constant array at new_min. Otherwise copy to float32, subtract min, divide by range, scale to [new_min, new_max], and clip in place.
I' = \frac{I - m}{M - m}(M' - m') + m'
Linear min–max remap of image I from [m, M] onto [m', M'].
Related: registration-tools-restore-range, normalize-ants-rigid-options
Restore Range
Keywords: denormalization, intensity, ANTs output, original dtype
Summary
Inverse of min_max_normalize: maps a normalized registered volume back to the subject's original intensity range. Applied after ANTs apply_transforms so saved NIfTI edits retain familiar HU or grayscale values for thresholding and visualization.
Considerations
Why use it
Puts a registered image back into its original intensity range after registration, so thresholds and display settings keep working on the result.
Use it when
- It runs automatically after registration.
Inputs / options
| Name | Type | Default | Description |
|---|
| image | np.ndarray | required | Normalized volume to restore. |
| original_min | number | required | Original intensity minimum to recover. |
| original_max | number | required | Original intensity maximum to recover. |
Algorithm / details
Normalize the current array to [0, 1], scale to [original_min, original_max], and clip in place — mirroring min_max_normalize in reverse.
I_{\mathrm{restored}} = \frac{I - m}{M - m}(M_0 - m_0) + m_0
Undo a temporary normalization using the original intensity bounds [m_0, M_0].
Related: registration-tools-min-max-normalize, align-to-reference
Save as Lossy NIfTI
Keywords: lossy compression, JPEG, preview, downsample, metadata
Summary
Creates a downsampled, JPEG-compressed 8-bit NIfTI preview plus an isometric projection PNG, and appends scale metadata to the subject JSON. Used after alignment, elastic registration, rotation, crop, and preprocessing so the web viewer can load fast previews without storing full-resolution lossless copies.
Considerations
Why use it
Creates the light preview copy (and preview image) of every new edit, so browsing stays fast while the full-resolution data is kept separately.
Use it when
- It runs automatically after most processing steps.
Why it works
The volume is downsampled by averaging blocks of voxels, mapped to 0–255 and compressed slice by slice. The scale used is stored so values can be mapped back.
How to check the result
- Remember the preview is averaged and compressed: judge fine detail and exact values at full resolution.
Inputs / options
| Name | Type | Default | Description |
|---|
| image_data | np.ndarray | required | Full-resolution 3D volume to compress. |
| voxel_size | number | required | Voxel spacing in mm for the output affine. |
| json_file | str | required | Path to subject metadata JSON; lossy_compression entry is appended. |
| output_file | str | required | Destination path for the lossy .nii.gz file. |
Algorithm / details
1) Read resolution_factor from json lossy_compression metadata (default 2). 2) Mean-pool voxels with block_reduce after edge padding. 3) Linearly map pooled data to 0–255, JPEG-compress each axial slice (quality 70), preserving exact zeros. 4) Generate isometric projection PNG(s) via VTK raycasting with weighted-sum fallback. 5) Save NIfTI and append scale_factor, zero_point_shift, resolution_factor to lossy_compression in JSON.
flowchart TD
A["Full-res volume"] --> B["Mean-pool by resolution_factor"]
B --> C["Map to 8-bit"]
C --> D["JPEG per slice"]
C --> E["Isometric projection PNG"]
D --> F["Save lossy NIfTI"]
F --> G["Update JSON metadata"]
Related: registration-tools-overview, align-to-reference
Calculate Affine Transform as Field
Keywords: affine registration, displacement field, ANTs, SyN, elastic initialization
Summary
Converts an ANTs Affine transform into a dense displacement field on the fixed grid so it can initialize SyNOnly (which accepts fields, not affine matrices, as initial_transform).
Considerations
Why use it
Turns an affine alignment into a deformation field so it can be the starting point of elastic (SyN) registration when affine initialisation is turned on.
Use it when
- It runs automatically when affine initialisation is enabled in elastic registration.
Why it works
An affine registration is computed, and for every voxel the displacement that affine would cause is stored as a vector field.
Inputs / options
| Name | Type | Default | Description |
|---|
| fixed_image_data | ants.ANTsImage | required | Fixed (reference) ANTs image. |
| moving_image_data | ants.ANTsImage | required | Moving ANTs image to align to fixed. |
Algorithm / details
1) Run ants.registration Affine (Global Correlation) between fixed and moving. 2) Recover A ∈ R^{3×3} and translation b from the ITK .mat; re-center about rotation center c as t = b + c − A c. 3) For each voxel index i, physical p = D(i ⊙ s) + o; store u(i) = (A p + t) − p. 4) Copy u into a properly headered vector NIfTI (placeholder SyN with reg_iterations = [1,0,0,0] then overwrite voxels).
\mathbf{t} = \mathbf{b} + \mathbf{c} - A\mathbf{c}
Re-center ITK translation b about rotation center c for the linear part A.
\mathbf{u}(\mathbf{i}) = (A\mathbf{p} + \mathbf{t}) - \mathbf{p}
Per-voxel displacement on the fixed grid; p = D(i ⊙ s) + o from direction, spacing, and origin.
flowchart TD
A["Fixed + moving ANTs images"] --> B["Affine registration"]
B --> C["Build physical voxel grid"]
C --> D["Apply affine per voxel"]
D --> E["Subtract to displacement field"]
E --> F["Write field NIfTI + return paths"]
Related: registration-tools-overview, registration-tools-min-max-normalize
Load NIfTI Mask Data
Keywords: mask, NIfTI, nii.mask.gz, elastic registration
Summary
Loads mask voxel data from a NIfTI path for elastic registration, including a temp-file workaround for Aurora's non-standard *.nii.mask.gz double suffix that NiBabel often cannot sniff. Used when reference and moving masks constrain the registration metric.
Considerations
Why use it
Reads Aurora's mask files for registration, working around their double file extension.
Use it when
- It runs automatically when registration uses masks.
Inputs / options
| Name | Type | Default | Description |
|---|
| mask_path | str | required | Filesystem path to the mask NIfTI (including *.nii.mask.gz). |
Outputs
| Name | Type | Description |
|---|
| (return) | ndarray | Floating-point voxel array from nib.load(...).get_fdata(). |
Algorithm / details
1) Try nib.load(mask_path).get_fdata(). 2) On ImageFileError, create a temporary file with suffix _mask_temp.nii.gz, copy the mask into it, load get_fdata from the temp path, then delete the temp file. 3) Return the voxel array to the caller.
flowchart TD
A["mask_path"] --> B["nib.load.get_fdata"]
B -->|"ok"| C["Return array"]
B -->|"ImageFileError"| D["Copy to *_mask_temp.nii.gz"]
D --> E["Load temp + delete"]
E --> C
Related: registration-tools-overview
Transform Landmarks by Rotation
Keywords: landmarks, rotation, crop, coordinate propagation
Summary
Propagates landmark coordinates through the same rotation, embedding offset, and final crop applied to a volume during manual rotation edits. Keeps landmark sidecars synchronized with geometric edits so downstream alignment and morphometrics stay consistent.
Considerations
Why use it
Moves landmarks with the same rotation and cropping that was applied to the volume, so they stay on the anatomy after a rotation edit.
Use it when
- It runs automatically after Apply Rotation.
Why it works
Each landmark is rotated with the volume and shifted by the same crop; landmarks that end up outside the new volume are dropped.
How to check the result
- After rotating, check that the landmark count is what you expect and points sit on the surface.
Inputs / options
| Name | Type | Default | Description |
|---|
| landmarks_list | list | required | Landmarks as [x,y,z] mm arrays or dicts with position and landmark_type. |
| voxel_size | number | required | Voxel spacing in mm. |
| rotation_matrix | np.ndarray (3,3) | required | Rotation matrix applied to the volume. |
| center_box | array-like (3,) | required | Rotation center in voxel space. |
| origin_offset | array-like (3,) | required | Embedding offset subtracted before rotation. |
| final_bbox_min | array-like (3,) | required | Minimum crop bounds in voxel space. |
| final_bbox_max | array-like (3,) | required | Maximum crop bounds in voxel space. |
| embedding_offset | array-like (3,) | required | Offset converting landmarks into the target embedding frame. |
Algorithm / details
For each landmark: convert mm → voxels, subtract embedding_offset, rotate about origin_offset, test inclusion in [final_bbox_min, final_bbox_max), subtract crop origin, convert back to mm. Landmarks outside the crop are excluded; survivors retain landmark_type.
v' = R(v - o)
v: landmark; o: origin_offset; R: rotation; keep v' inside the final bounding box.
flowchart TD
A["Landmarks in mm"] --> B["To voxel space"]
B --> C["Apply embedding offset"]
C --> D["Rotate"]
D --> E{"Inside crop?"}
E -->|"yes"| F["Subtract crop origin to mm"]
E -->|"no"| G["Exclude landmark"]
Related: registration-tools-transform-landmarks-by-crop, invert-alignment-landmarks
Transform Landmarks by Crop
Keywords: landmarks, crop, padding, coordinate propagation
Summary
Updates landmark positions after a volumetric crop and padding operation. Used when subjects are cropped to a region of interest so landmark JSON sidecars remain aligned with the edited volume geometry.
Considerations
Why use it
Moves landmarks with a crop and padding so they stay on the anatomy, and drops the ones outside the kept region.
Use it when
- It runs automatically after Apply Crop.
How to check the result
- After cropping, check that no landmark you need was dropped.
Inputs / options
| Name | Type | Default | Description |
|---|
| landmarks_list | list | required | Landmarks as [x,y,z] mm arrays or dicts with position and landmark_type. |
| voxel_size | number | required | Voxel spacing in mm. |
| z_min | number | required | Crop minimum along axis 0 (voxel indices). |
| z_max | number | required | Crop maximum along axis 0 (voxel indices). |
| y_min | number | required | Crop minimum along axis 1 (voxel indices). |
| y_max | number | required | Crop maximum along axis 1 (voxel indices). |
| x_min | number | required | Crop minimum along axis 2 (voxel indices). |
| x_max | number | required | Crop maximum along axis 2 (voxel indices). |
| padding | number | required | Padding added in voxels after cropping. |
Algorithm / details
Convert each landmark from mm to voxels; if inside the crop box [z_min,z_max) × [y_min,y_max) × [x_min,x_max), subtract crop origin, add padding offset, convert back to mm and retain landmark_type; otherwise exclude the landmark.
v_{\mathrm{crop}} = v - (z_0, y_0, x_0) + p
Shift landmarks into the cropped frame; p: padding along each axis.
flowchart TD
A["Landmarks in mm"] --> B["To voxels"]
B --> C{"Inside crop ROI?"}
C -->|"yes"| D["Subtract crop origin add padding"]
D --> E["Back to mm"]
C -->|"no"| F["Exclude"]
Related: registration-tools-transform-landmarks-by-rotation