{
  "source_file": "0930",
  "source_platform": "Hashnode",
  "author": "Lakshmi Mahabaleshwara",
  "title_listed": "How to Turn a RECIST Line into a 3D Tumor Segmentation Mask",
  "keywords": "",
  "domain": "图像处理",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://freecodecamp.org/news/how-to-turn-a-recist-line-into-a-3d-tumor-segmentation-mask",
  "publish_date": "2026-09-18",
  "corpus_stage": "",
  "notes": "",
  "id": "8425317f7a43",
  "collected_at": "2026-10-02T14:11:46",
  "title": "How to Turn a RECIST Line into a 3D Tumor Segmentation Mask",
  "body_markdown": "A radiologist can mark a tumor on a CT scan by drawing a straight line across it. Creating a complete 3D segmentation requires outlining the tumor across the slices where it appears, which is more time-consuming.\nThis tutorial explains how Lumina works. It's a system that uses a CT scan and one RECIST line to produce a 3D segmentation mask of the marked tumor.\nLumina was developed for the\nFLARE 2026 pan-cancer segmentation challenge\n. The system was designed to run on a CPU with an 8 GB memory limit and a 60-second inference limit.\nThis tutorial covers the main design decisions, implementation details, and experiments that shaped the final system.\nWhat We'll Cover:\nWhat Lumina Does\nLumina Pipeline Overview\nPrerequisites\nStep 1: Review Your Data Before Coding\nThe Scans Were Already Brightness-Adjusted\nCoordinate Order Matters\nStep 2: Turn the RECIST Line into Network Input\nDraw the Line at the Required Resolution\nStep 3: Align All Tumors on a Common Grid\nIntensity Normalization\nStep 4: Build the 3D Segmentation Network\nStep 5: Use a Loss Function That Includes Boundary Information\nComparing Boundary Losses\nStep 6: Transform the Probability Map into a Segmentation Mask\nResample the Probability Map\nApply the Threshold\nKeep the Tumor Connected to the RECIST Line\nStep 7: Accelerate and Ensure Consistency in CPU Inference\nPin the Thread Count\nBudget the Inference Passes\nHandle Individual Failures\nThe Results\nQualitative results\nWhat the Radiologist Review Showed\nThree Lessons That Apply to Other Projects\n1. Make Your Validation Data Representative\n2. Measure the Limits of Your Preprocessing\n3. Record Where Your Numbers Come From\nConclusion\nWhat Lumina Does\nA radiologist can measure a tumor by drawing a line across its longest visible diameter. This is a\nRECIST measurement\n(Response Evaluation Criteria in Solid Tumors), which is widely used to measure tumor response during cancer treatment.\nA RECIST line measures a 2D diameter marker. It doesn't describe the tumor's full 3D shape.\nLumina uses this measurement to prompt a 3D segmentation model.\nThe task can be described as:\nInput:\nA 3D CT scan and a 2D RECIST line marking one tumor.\nOutput:\nA 3D mask of that tumor, with one label for each voxel.\nA\nvoxel\nis the 3D equivalent of a pixel. Each voxel represents a small volume of tissue in the CT scan.\nThe RECIST line tells the model which tumor to segment. The model then predicts the tumor's three-dimensional extent.\nThis makes the segmentation problem more specific because the model doesn't need to identify every possible tumor in the scan.\nLumina Pipeline Overview\nThe complete process has several stages. We start with a 3D CT scan and a RECIST line marking one tumor. We then convert the line into additional input channels, crop and resample the image, and pass the three channels through a 3D segmentation network.\nThe network produces a probability map, which we convert into the final 3D tumor mask through resampling, thresholding, and connected-component selection.\nThe main stages are:\nPrepare the CT scan and RECIST marker.\nEncode the RECIST line as additional network input.\nCrop and resample the image to a common grid.\nPredict the tumor probability map with a 3D segmentation network.\nRefine the probability map and convert it into a binary mask.\nControl CPU inference so the full case stays within runtime and memory limits.\nPrerequisites\nYou'll get more from this tutorial if you have some experience with:\nPython\nNumPy\nPyTorch\nBasic convolutional neural networks.\nNo detailed medical background is required. The tutorial explains medical imaging concepts as it introduces them.\nThe implementation uses NumPy, SciPy, PyTorch, and\nMONAI\n, a medical imaging framework built on PyTorch.\nStep 1: Review Your Data Before Coding\nOur data was provided as\n.npz\nfiles, which is NumPy's compressed array format.\nEach file contains fields similar to these:\nimgs       # the CT scan, a 3D array\nrecist     # the marker lines, same shape, one integer per tumour\nspacing    # how many millimetres apart the voxels are\norigin     # where the scan sits in the scanner's coordinates\ndirection  # how the scan is rotated\ngts        # the ground-truth segmentation, training files only\nBefore building the model, we inspected the image values, array shapes, and spatial metadata.\nTwo details were especially significant.\nThe Scans Were Already Brightness-Adjusted\nCT scanners normally store images in Hounsfield units. Water is about 0 HU, while bone can exceed 1000 HU.\nIn this dataset, the scans had already been converted to a fixed 0–255 range. The original Hounsfield-unit values weren't available.\nThis meant that we couldn't apply the usual CT windowing process to the original values. Instead, we measured the intensity distribution of the data provided.\nFor example, 53.4% of the voxels had a value of exactly 0, corresponding to air outside the body.\nCoordinate Order Matters\nThe\nspacing\narray is stored as\n(X, Y, Z)\n, while NumPy arrays are indexed as\n(Z, Y, X)\n.\nIf you mix these two conventions, spatial measurements can be incorrect.\nWe converted the coordinates once at the input boundary and used the\n(Z, Y, X)\nconvention internally:\ndef _to_zyx(vec3, order):\n    vec3 = np.asarray(vec3, dtype=float).ravel()\n    if order == \"xyz\":\n        return vec3[::-1].copy()      # (x, y, z) -> (z, y, x)\n    if order == \"zyx\":\n        return vec3.copy()\n    raise ValueError(f\"unknown geometry_order {order!r}\")\nThe important lesson is to inspect several files before designing the preprocessing pipeline.\nCheck:\nArray shapes\nIntensity ranges\nVoxel spacing\nCoordinate conventions\nMetadata\nAvailable labels\nAvoid assuming the data follows conventions from another CT dataset or tutorial.\nStep 2: Turn the RECIST Line into Network Input\nA neural network receives a stack of input channels. The CT scan provides the first channel. We then represent the RECIST line in a form the network can use.\nWe use three channels:\nCT scan\nRECIST line, drawn 3 voxels thick\nEndpoint heatmap, containing a Gaussian around each endpoint\nThe two endpoints define the measured diameter and provide the network with the location and length of the RECIST measurement.\nThe endpoint information is represented using Gaussian functions. Each Gaussian has a high value near the endpoint and gradually decreases with distance.\ndef _endpoint_heatmap(endpoints_zyx, shape, sigma):\n    d, h, w = (int(s) for s in shape)\n    heat = np.zeros((d, h, w), dtype=np.float32)\n\n    rad = max(1, int(np.ceil(3 * sigma)))\n    two_s2 = 2.0 * sigma * sigma\n\n    for z0, y0, x0 in np.asarray(endpoints_zyx, float):\n        zc, yc, xc = int(round(z0)), int(round(y0)), int(round(x0)))\n\n        zl, zr = max(0, zc - rad), min(d, zc + rad + 1)\n        yl, yr = max(0, yc - rad), min(h, yc + rad + 1)\n        xl, xr = max(0, xc - rad), min(w, xc + rad + 1)\n\n        zz, yy, xx = np.mgrid[\n            zl:zr, yl:yr, xl:xr\n        ].astype(np.float32)\n\n        g = np.exp(\n            -((zz-z0)**2 + (yy-y0)**2 + (xx-x0)**2) / two_s2\n        )\n\n        np.maximum(\n            heat[zl:zr, yl:yr, xl:xr],\n            g,\n            out=heat[zl:zr, yl:yr, xl:xr]\n        )\n\n    return heat\nThe Gaussian is calculated only in a small region around each endpoint.\nWith\nsigma = 1.5\n, values become very small several voxels away from the endpoint. Calculating the Gaussian over the complete volume would therefore perform unnecessary work.\nDraw the Line at the Required Resolution\nWe also redraw the RECIST line from its two endpoints at the resolution the network requires.\nWe don't create the line at one resolution and then resize it with the image. Resizing a thin line can make it thinner or cause it to disconnect. Recreating the line from its endpoints keeps the prompt consistent with the image resolution.\nStep 3: Align All Tumors on a Common Grid\nCT scans can have different voxel spacings.\nFor example, one scan may have thin slices while another may have much thicker slices. The physical size represented by the same number of voxels can therefore vary between scans.\nFor each marked tumor, we'll create a fixed-size crop and resample it to a fixed voxel spacing.\nOur configuration is:\ntarget_spacing: [2.5, 1.0, 1.0]     # mm per voxel: z, y, x\ncrop_size:      [64, 160, 160]      # voxels: z, y, x\nThe in-plane dimensions correspond to a physical field of view of:\n160 × 160 mm\nThe network therefore receives a consistent\n64 × 160 × 160\ninput.\nWe use a target spacing derived from the training data. The 2.5 mm slice spacing was close to the median slice spacing in the training set.\nWe use a 160 mm crop based on the distribution of lesion sizes in the training data. It covers the 99th percentile of lesion extent.\nIntensity Normalization\nWe also normalize the CT intensity values using statistics calculated from the training crops:\nintensity_mean: 96.88\nintensity_std: 79.00\nThe normalization is:\nimage = (image - 96.88) / 79.00\nWe calculate these statistics using only the training data.\nWe use the same values for validation and test data. We don't recalculate these statistics from the validation or test sets, because that would allow information from those sets to influence preprocessing.\nFor Lumina, the training-data statistics are a mean of\n96.88\nand a standard deviation of\n79.00\n. We store these values in the configuration and use the same preprocessing during training, validation, and inference.\nStep 4: Build the 3D Segmentation Network\nHere, we'll use DynUNet from MONAI.\nDynUNet is a 3D U-Net architecture based on ideas from nnU-Net and supports configurable network depth, kernels, strides, and residual connections.\nA U-Net has two main parts.\nThe encoder gradually reduces the spatial resolution while learning increasingly high-level features.\nThe decoder then restores the spatial resolution to produce a segmentation map.\nSkip connections pass fine spatial details from the encoder to the decoder. This helps the decoder recover the tumor's boundary when it creates the final segmentation.\nFor Lumina, the network receives three channels:\nCT\nRECIST line\nEndpoint heatmap.\nThe model configuration is:\nfrom monai.networks.nets import DynUNet\n\n# 5 levels; the first downsample skips z (see below).\nkernels = [[3, 3, 3]] * 5\nstrides = [[1, 1, 1], [1, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]]\n\nmodel = DynUNet(\n    spatial_dims=3,\n    in_channels=3,          # CT + line + endpoint blobs\n    out_channels=1,         # one probability per voxel\n    kernel_size=kernels,\n    strides=strides,\n    upsample_kernel_size=strides[1:],\n    filters=features,\n    res_block=True,\n)\nThe\nstrides\nlist is an important part of the configuration. Our voxels have different sizes: they're 2.5 mm apart between slices but only 1.0 mm apart within each slice. If we reduce all three dimensions at every level, we lose inter-slice detail too quickly. We'll therefore keep the z dimension unchanged during the first downsampling step. This helps preserve useful information in all three directions.\nLumina's main design work focuses on input representation, preprocessing, training strategy, post-processing, and CPU inference behavior.\nStep 5: Use a Loss Function That Includes Boundary Information\nDuring training, the network needs a way to measure how different its prediction is from the ground-truth segmentation. This measurement is called a\nloss function\n. The network adjusts its weights to reduce this loss.\nFor Lumina, we combine two common losses:\nDice loss\nand\nbinary cross-entropy (BCE)\n.\nDice Loss\nDice loss focuses on the overlap between the predicted tumor region and the ground-truth region.\nIt's useful when we want the overall size and shape of the predicted region to match the reference segmentation.\nBinary Cross-entropy\nBinary cross-entropy works at the voxel level. For every voxel, the network predicts a probability between 0 and 1.\nThe ground-truth value is:\n1 if the voxel belongs to the tumor\n0 if the voxel belongs to the background.\nBCE penalizes the network when its predicted probability differs from the ground-truth value.\nFor example, if a tumor voxel has a predicted probability close to\n1\n, the penalty is small. If the network confidently predicts a tumor voxel as background, the penalty is larger.\nWe combine the two losses by adding them:\nloss = dice_loss + bce_loss\nThe two terms provide different training signals:\nDice loss: encourages good overall region overlap.\nBCE: encourages correct individual voxel predictions.\nAdding Boundary Information\nDice and BCE don't give the lesion boundary any special treatment.\nThis matters because a segmentation can have good overall overlap while still having an inaccurate boundary.\nLumina is evaluated using both:\nDice\n, which measures region overlap\nNSD (Normalized Surface Dice)\n, which measures surface agreement within a specified distance\nThe official challenge tolerance is\n1 mm\n.\nTo give the network additional information about the boundary, we add a boundary-band loss term.\nWe create a thin band around the ground-truth surface by expanding and shrinking the target mask:\nshell = dilate(target) & ~erode(target)\n\nloss = (\n    dice_loss\n    + bce_loss\n    + 0.5 * bce_loss_on(shell)\n)\nThe three terms therefore have different roles:\nDice loss: overall region overlap\nBCE: voxel-level prediction accuracy\nBoundary-band loss: extra attention to voxels near the lesion surface.\nThe additional boundary term is weighted by 0.5.\nComparing Boundary Losses\nMONAI also provides\nHausdorffDTLoss\n, which uses a distance-transform-based formulation.\nWe can compare the computational cost of the different approaches during training:\nLoss\nTime per step\nDice + cross-entropy\n7 ms\nOurs (boundary band)\n203 ms\nMONAI HausdorffDTLoss\n957 ms\nThe fast path for the Hausdorff distance loss required the\ncupy\nlibrary, which couldn't be built in our training environment. Its CPU implementation was considerably more expensive.\nOur boundary-band approach uses morphological operations based on max-pooling and has a lower computational cost.\nThe boundary term improved Dice on large tumors by\n0.0136\n. On small tumors, the results were mixed: 15 cases improved and 13 worsened.\nStep 6: Transform the Probability Map into a Segmentation Mask\nThe segmentation network produces a probability map. Each voxel in this map contains a value between 0 and 1, representing how likely that voxel is to belong to the tumor. We can now convert this probability map into the final 3D segmentation mask. There are three steps:\nResample the probability map back to the original CT grid.\nApply a threshold to create a binary mask.\nKeep the connected component associated with the RECIST line.\nResample the Probability Map\nDuring preprocessing, we crop and resample the CT image to a fixed size of\n64 × 160 × 160\nvoxels. The network therefore produces its prediction on this processed grid.\nBefore creating the final mask, we resample the\nprobability map\nback to the original CT image grid.\nprob_original = resample_to_original_grid(\n    probability_map,\n    original_image\n)\nWe do this before thresholding so that the probability values can be interpolated on the original grid. This allows the final boundary to be represented more accurately.\nApply the Threshold\nThe network produces a probability between\n0\nand\n1\nfor every voxel. We use a threshold of\n0.35\nto convert this probability map into a binary segmentation mask.\nmask = prob_original >= 0.35\nVoxels with a probability of at least\n0.35\nbecome part of the predicted tumor, while the remaining voxels are treated as background.\nThe threshold of\n0.35\nwas selected during Lumina's validation experiments and is part of the final inference pipeline.\nKeep the Tumor Connected to the RECIST Line\nThe thresholded mask can contain small disconnected regions. Some of these regions may not belong to the tumor.\nBecause we know where the tumor was marked, we use the RECIST line to select the relevant connected component.\ncomponents = connected_components(mask)\n\ntumor_mask = select_component(\n    components,\n    recist_line\n)\nWe retain the component that intersects the RECIST line as the final tumor segmentation.\nIf the RECIST line doesn't intersect any component after thresholding, we use the component closest to the line midpoint as a fallback.\nThe order of these operations is important:\nProbability map\n      ↓\nResample to original CT grid\n      ↓\nApply threshold (0.35)\n      ↓\nConnected-component selection\n      ↓\nFinal 3D tumor mask\nWhy Do We Resample Before Thresholding?\nWe resample the\nprobability map before thresholding\nso that the probability values can be interpolated on the original CT grid. During development, we also tested thresholding before resampling. That approach produced small changes at the lesion boundary because interpolation was applied to an already binary mask.\nUsing the probability map, preserves more information during interpolation and gives the final segmentation a more precise boundary.\nThe result is a 3D binary mask aligned with the original CT scan, ready for evaluation or visualization.\nStep 7: Accelerate and Ensure Consistency in CPU Inference\nThe CPU and memory limits influence the inference design.\nWe use several techniques to keep inference within the challenge constraints.\nPin the Thread Count\nCPU operations can produce small numerical differences when calculations run with different thread counts.\nNear a segmentation threshold such as\n0.35\n, very small changes in probability can affect whether a voxel is included in the final mask.\nWe therefore explicitly set the PyTorch thread count:\ntorch.set_num_threads(8)\nUsing the same thread configuration across runs helps keep the inference process reproducible.\nBudget the Inference Passes\nWe use an ensemble because combining predictions from several model passes improves the segmentation results.\nThe ensemble uses predictions from different inputs, including flipped versions of the image and a separately trained SegResNet model.\nThe number of lesions can vary from one scan to another. If we use four passes for every lesion, a scan with five lesions requires 20 model passes. This can exceed the challenge's runtime limit.\nWe therefore set a maximum number of passes for each scan and adjust the number of passes based on the number of marked lesions.\nA simplified version is:\nwant = max(\n    1,\n    min(len(members) + 1, cap // max(len(ids), 1))\n)\nNumber of tumors\nPasses per tumor\n1\n4\n2\n3\n3\n2\nMore than 3\n1\nThe plan is determined by the number of markers before inference starts. This keeps the amount of computation predictable and avoids making the result depend on how much time happens to remain during execution.\nThe measured results were:\nSetting\nScore\nNo ensemble\n0.7242\nCap 4\n0.7324\nCap 6\n0.7361\nNo cap (4 passes always)\n0.7410\nThe unrestricted ensemble produced the highest score but didn't satisfy the runtime constraint. A cap of 6 retained much of the ensemble improvement while keeping inference within the required limit.\nHandle Individual Failures\nWhen processing multiple cases, one failed case shouldn't stop the entire pipeline.\nIf a case can't be processed, we generate an empty mask and log the error. The pipeline then continues with the remaining cases.\nThis allows the complete batch to finish even when an individual case has a problem.\nThe Results\nOn the 217 hidden test scans, Lumina achieved:\nDice: 0.7619\nNSD: 0.6094\nThe NSD value uses the official\n1 mm tolerance\n.\nThe median inference time per case was\n20.3 seconds\n. The slowest observed runtime was\n31.8 seconds\n.\nPeak memory usage was\n2.22 GB\ninside the 8 GB container.\nThe Docker image was approximately\n559 MB\n.\nQualitative results\nLarge lesion with a clear boundary\nThe first example is a large lesion with a volume of 272.9 cm³. It achieved:\nDSC: 0.961\nNSD: 0.850\nThe lesion is well circumscribed, with a clear interface with the surrounding fat.\nThe predicted contour follows the reference boundary closely across the visible extent of the lesion.\nMedium lesion with an uncertain boundary\nThe second example is a medium lesion with a volume of 28.4 cm³. It achieved:\nDSC: 0.563\nNSD: 0.114\nThe prediction covers a larger area than the reference annotation.\nRadiologist review indicated that this type of case can have uncertainty in the reference boundary itself. Therefore, a low numerical score doesn't by itself establish that the predicted contour is clinically unacceptable.\nWhat the Radiologist Review Showed\nWe also reviewed 30 lesions with a radiologist to better understand the types of errors Lumina made.\nThe review showed that lesion size wasn't the only factor affecting segmentation.\nLesions with clear boundaries were generally easier to segment. More difficult cases had poorly defined margins or a similar appearance to the surrounding tissue.\nThe main errors were:\nUnder-segmentation: part of the lesion was missed.\nOver-segmentation: surrounding tissue was included.\nBoundary errors: the predicted contour did not follow an unclear or infiltrative margin.\nThe review also showed that numerical metrics should be interpreted together with the visible lesion boundary.\nIn some cases, the reference boundary itself was difficult to define. A low score therefore didn't necessarily mean that the predicted contour was clinically unacceptable.\nThis is consistent with the lower surface accuracy observed in difficult lesions, where small boundary differences can strongly affect NSD.\nThree Lessons That Apply to Other Projects\n1. Make Your Validation Data Representative\nA validation set should reflect the distribution that the final system will be evaluated on.\nOur internal validation split came from the training data. Its median tumor volume was\n814 mm³\n.\nThe competition scoring set had a median tumor volume of\n16,805 mm³\n, which was much larger.\nThis difference affected how some improvements appeared during development.\nFor example, the boundary loss showed little improvement on the internal validation set for several epochs. On the public validation distribution, which contained larger lesions, its effect was more useful.\nIf an improvement targets a particular part of the data distribution, that distribution should be represented in validation.\n2. Measure the Limits of Your Preprocessing\nThe 160 mm crop was an important design choice in Lumina.\nSome large lesions extended beyond this field of view.\nBefore changing the crop size, we measured the error introduced by cropping and resampling.\nWe passed the ground-truth masks through the same crop-and-resample pipeline and measured the resulting surface score.\nFor large lesions, the geometry ceiling was\n0.9699 NSD\n, while the model achieved\n0.5353 NSD\n.\nThis showed that the preprocessing pipeline preserved the lesion surface reasonably well. Preprocessing alone did not explain the remaining gap.\nWe also tested several approaches for increasing the field of view, including:\nOverlapping tiles\nAdaptive zoom\nA larger crop\nThese changes didn't improve the final score.\nMeasuring the preprocessing ceiling helped us focus further development on the model and inference pipeline.\n3. Record Where Your Numbers Come From\nModel development involves many configuration values:\nThresholds\nCrop sizes\nVoxel spacing\nLoss weights\nSampling weights\nEnsemble settings\nRuntime limits\nEach value should have a clear source.\nFor example, record:\nWhat was measured\nWhich dataset was used\nWhen the measurement was made\nWhich alternatives were tested\nWhy the final value was selected.\nThis makes it easier to reproduce experiments and understand design decisions later.\nConclusion\nLumina shows that a single RECIST line can reconstruct a tumor's 3D extent from a CT scan.\nThe system combines prompt-based input channels, a fixed physical crop, a 3D DynUNet, boundary-aware training, probability-based post-processing, and controlled CPU inference.\nOn the 217 held-out cases, Lumina achieved a\nDice score of 0.7619\nand an\nNSD of 0.6094\nat the official 1 mm tolerance.\nThe median inference time was\n20.3 seconds\n, with a peak memory usage of\n2.22 GB\n, keeping the system within the challenge constraints.\nThe experiments also showed that boundary accuracy remains an important area for improvement, particularly for large and less clearly defined lesions.\nThe geometry-ceiling analysis showed that the crop and resampling pipeline preserved the lesion surface well, indicating that further improvements should focus on segmentation accuracy and robustness.\nOverall, Lumina demonstrates a practical approach for converting a 2D RECIST measurement into a 3D lesion segmentation while meeting strict computational constraints.",
  "metadata": {
    "title": "How to Turn a RECIST Line into a 3D Tumor Segmentation Mask",
    "description": "A radiologist can mark a tumor on a CT scan by drawing a straight line across it. Creating a complete 3D segmentation requires outlining the tumor across the slices where it appears, which is more tim",
    "keywords": null,
    "author": null,
    "og:site_name": "freeCodeCamp.org",
    "og:type": "article",
    "og:title": "How to Turn a RECIST Line into a 3D Tumor Segmentation Mask",
    "og:description": "A radiologist can mark a tumor on a CT scan by drawing a straight line across it. Creating a complete 3D segmentation requires outlining the tumor across the slices where it appears, which is more tim",
    "og:url": "https://www.freecodecamp.org/news/how-to-turn-a-recist-line-into-a-3d-tumor-segmentation-mask/",
    "og:image": "https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/bde6732f-ad81-4a51-8939-ec74637d09e6.png",
    "og:image:width": "1920",
    "og:image:height": "1080",
    "twitter:card": "summary_large_image",
    "twitter:title": "How to Turn a RECIST Line into a 3D Tumor Segmentation Mask",
    "twitter:description": "A radiologist can mark a tumor on a CT scan by drawing a straight line across it. Creating a complete 3D segmentation requires outlining the tumor across the slices where it appears, which is more tim",
    "twitter:url": "https://www.freecodecamp.org/news/how-to-turn-a-recist-line-into-a-3d-tumor-segmentation-mask/",
    "twitter:image": "https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/bde6732f-ad81-4a51-8939-ec74637d09e6.png",
    "twitter:label1": "Written by",
    "twitter:data1": "Lakshmi Mahabaleshwara",
    "twitter:label2": "Filed under",
    "twitter:data2": "MachineLearning, Deep Learning, Artificial Intelligence, Medical Imaging, Computer Vision, HealthcareAI",
    "twitter:site": "@freecodecamp",
    "article:published_time": "2026-09-18T21:32:56.152Z",
    "article:modified_time": "2026-09-18T21:32:56.152Z",
    "article:tag": "HealthcareAI",
    "article:publisher": "https://www.facebook.com/freecodecamp"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/8425317f7a43/code/body-000.txt",
      "chars": 321,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-001.txt",
      "chars": 269,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-002.txt",
      "chars": 839,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-003.txt",
      "chars": 114,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-004.txt",
      "chars": 13,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-005.txt",
      "chars": 43,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-006.txt",
      "chars": 32,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-007.txt",
      "chars": 466,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-008.txt",
      "chars": 28,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-009.txt",
      "chars": 112,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-010.txt",
      "chars": 85,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-011.txt",
      "chars": 29,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-012.txt",
      "chars": 106,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-013.txt",
      "chars": 150,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-014.txt",
      "chars": 25,
      "language": "text"
    },
    {
      "file": "attachments/8425317f7a43/code/body-015.txt",
      "chars": 72,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/ac6b8049-9e84-4e68-803f-8f04f92adb04.png",
      "bytes": 94369,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/003.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/ac6b8049-9e84-4e68-803f-8f04f92adb04.png",
      "alt": "Axial CT image of the abdomen with a green diameter marker placed across a liver lesion.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/dc5e85bd-a319-41e7-be93-0686bb8acea8.png",
      "bytes": 141621,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/004.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/dc5e85bd-a319-41e7-be93-0686bb8acea8.png",
      "alt": "Diagram showing the Lumina pipeline from a CT scan and RECIST line to a 3D tumor segmentation mask.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/e67ad26c-a952-4e99-8f45-463667e5de7b.png",
      "bytes": 224368,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/005.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/e67ad26c-a952-4e99-8f45-463667e5de7b.png",
      "alt": "CT image and the two RECIST prompt channels used as input to Lumina: the RECIST line and endpoint heatmap.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/bad771b7-b8bb-4fc8-ab33-e17d95684419.png",
      "bytes": 151306,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/006.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/bad771b7-b8bb-4fc8-ab33-e17d95684419.png",
      "alt": "Simplified DynUNet architecture showing the encoder, decoder, bottleneck, and skip connections used to preserve spatial information.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/5c02d6d1-2f89-45e3-a5d6-06524acdb5ea.png",
      "bytes": 105051,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/007.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/5c02d6d1-2f89-45e3-a5d6-06524acdb5ea.png",
      "alt": "5c02d6d1-2f89-45e3-a5d6-06524acdb5ea",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/62db2be9-9f49-4aa2-a416-5def38dc651f.png",
      "bytes": 362860,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/008.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/62db2be9-9f49-4aa2-a416-5def38dc651f.png",
      "alt": "CT images of a well-circumscribed large lesion with a clear boundary; predicted segmentation closely follows the reference, with DSC 0.961 and NSD 0.850.",
      "downloaded": true
    },
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/b680de43-a438-41c4-a31d-7465577db343.png",
      "bytes": 294894,
      "reused": true,
      "local_path": "attachments/8425317f7a43/images/009.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/b680de43-a438-41c4-a31d-7465577db343.png",
      "alt": "CT images of a medium lesion where the prediction covers a larger region than the reference annotation; the boundary is unclear, with DSC 0.563 and NSD 0.114.",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://www.codabench.org/competitions/17652/",
      "label": "FLARE 2026 pan-cancer segmentation challenge",
      "type": "a"
    },
    {
      "href": "https://project-monai.github.io/",
      "label": "MONAI",
      "type": "a"
    }
  ],
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:50:57Z",
  "source_url": "https://freecodecamp.org/news/how-to-turn-a-recist-line-into-a-3d-tumor-segmentation-mask",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 24608,
  "expected_body_images": 7,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/8425317f7a43/pages/source.html",
  "local_html": "attachments/8425317f7a43/pages/article.html",
  "body_base_path": "attachments/8425317f7a43/pages/",
  "body_markdown_path": "attachments/8425317f7a43/pages/article.md"
}