OSCR

Protocol for 3D digital dynamic histomorphometry of mouse bone via time-lapse registration of serial microCT scans.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [1] § Troubleshooting › Problem 5: Definition of marrow space (step 22) ↔ Meslier_3DDynamicHisto_QM_r1.m, lines 131–195 · score 0.71 · bone marrow space, blood vessel, marrow area, outer
  2. [2] § Expected outcomes ↔ Meslier_3DDynamicHisto_QM_r1.m, lines 664–712 · score 0.60 · periosteal surfaces, bone volumes, bone formation, cortical, endosteal, pre
  3. [3] § Troubleshooting › Potential solution ↔ Meslier_3DDynamicHisto_QM_r1.m, lines 131–195 · score 0.60 · bone marrow space, blood vessel, slice, mask
  4. [4] § Step-by-step method details › MATLAB rendering and extra parameters quantification (optional) ↔ Meslier_3DDynamicHisto_QM_r1.m, lines 197–257 · score 0.52 · Perimeter length, endosteal surfaces, marrow, tibiae, periosteal, slices

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 1,159 lines · 48 KB · MIT · 4 matches

  1. clc
  2. clear all
  3. close all
  4. %% Initialization
  5. Folder="624" % >>> Enter your sample number <<<< (which is also the folder name containing the binary files for this sample)
  6. directory='C:/Users/username/Box/..../Experimental_Folder/Samples/'; %direcotry of the binary files for the Midshaft, distal, or proximal ROI
  7. days =18; % >>>> Enter the number of days between Pre and Post scans <<<< (to calculate rates) according to your experiment
  8. Mid=0; % >>> what region are you processing ? change from 0 to 1 <<<<
  9. Distal=1;
  10. Prox=0;
  11. % Trab_only=1; % Need Prox =1 to work
  12. nowing=0; % change to 1 if the tibial ridge has been removed
  13. starting_slice=1; %define the first slice of the stack to be analyzed
  14. size_ROI=100; % number of slices included in the region to be analyzed
  15. pixel_xy_um=10.5; % pixel size in xy
  16. pixel_z_um=10.5; % voxel depth
  17. voxelVolume_um3 = pixel_xy_um^2 * pixel_z_um; % voxel volume
  18. directory_excelfile='C:/Users/username/Box/..../Experimental_Folder/Meslier_Results_3DDynamicHisto_copy.xlsx'; %Excel file directory (results export)
  19. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  20. % read image
  21. if Mid==1 && nowing==0
  22. Data_Formation=tiffreadVolume(directory+ Folder + '/Mid/F.tiff'); % sometimes need to change to .tif or .tiff depending if saved from fiji or from dragonfly
  23. Data_Resorption=tiffreadVolume(directory+ Folder + '/Mid/R.tiff');
  24. Data_WholeBone=tiffreadVolume(directory+ Folder + '/Mid/WB.tiff');
  25. Data_Prebone=tiffreadVolume(directory+ Folder + '/Mid/PreBone.tiff');
  26. end
  27. %
  28. if Mid==1 && nowing==1
  29. Data_Formation=tiffreadVolume(directory+ Folder + '/Mid/F_nowing.tif'); % sometimes need to change to .tif or .tiff depending if saved from fiji or from dragonfly
  30. Data_Resorption=tiffreadVolume(directory+ Folder + '/Mid/R_nowing.tif');
  31. Data_WholeBone=tiffreadVolume(directory+ Folder + '/Mid/WB_nowing.tif');
  32. Data_Prebone=tiffreadVolume(directory+ Folder + '/Mid/PreBone_nowing.tif');
  33. end
  34. %
  35. if Prox==1
  36. Data_Formation=tiffreadVolume(directory+ Folder + '/Proxi/F.tiff');
  37. Data_Resorption=tiffreadVolume(directory+ Folder + '/Proxi/R.tiff');
  38. Data_WholeBone=tiffreadVolume(directory+ Folder + '/Proxi/WB.tiff');
  39. Data_Prebone=tiffreadVolume(directory+ Folder + '/Proxi/PreBone.tiff');
  40. end
  41. if Distal==1
  42. Data_Formation=tiffreadVolume(directory+ Folder + '/Distal/F.tiff');
  43. Data_Resorption=tiffreadVolume(directory+ Folder + '/Distal/R.tiff');
  44. Data_WholeBone=tiffreadVolume(directory+ Folder + '/Distal/WB.tiff');
  45. Data_Prebone=tiffreadVolume(directory+ Folder + '/Distal/PreBone.tiff');
  46. end
  47. % define ROI (right now 101 slices, could be changed)
  48. s_stack=size(Data_Prebone);
  49. size_stack=s_stack(3);
  50. cut=starting_slice; % region to be analyzed (101 slices, 5 slices down from the first proximal image) change to 30 for distal
  51. Data_Formation=Data_Formation(:,:,cut:cut+size_ROI);
  52. Data_Resorption=Data_Resorption(:,:,cut:cut+size_ROI);
  53. Data_WholeBone=Data_WholeBone(:,:,cut:cut+size_ROI);
  54. PreBone=Data_Prebone(:,:,cut:cut+size_ROI);
  55. % Use WholeBone Mask to create mask for Outer and Inner surfaces
  56. start =1;
  57. stop=length(Data_WholeBone(1,1,:));
  58. Outer_perim=[];
  59. voxelVolume_um3 = pixel_xy_um^2 * pixel_z_um;
  60. Data_Whole_bin = false(size(Data_WholeBone));
  61. Data_WholeBone_nofibend = false(size(Data_WholeBone));
  62. PreBone_nofibend_3d = false(size(PreBone)); % <-- store per-slice
  63. Marrow_3d = false(size(Data_WholeBone));
  64. PreBoneFill_3d = false(size(PreBone)); % outer surface mask
  65. %initialization of parameters
  66. se_1_d= strel('diamond',1);
  67. se_1= strel('Disk',1);
  68. se_1_sqr= strel('square',1);
  69. se_2= strel('Disk',2);
  70. se_3= strel('Disk',3);
  71. se_3_sqr= strel('square',3);
  72. se_4= strel('Disk',4);
  73. se_5= strel('Disk',5); %define size of closing parameter (See below)
  74. se_6= strel('Disk',6);
  75. se_7= strel('Disk',7);
  76. se_10= strel('Disk',10);
  77. for i=start:stop
  78. Data_Whole_bin(:,:,i)=imbinarize(Data_WholeBone(:,:,i));
  79. Data_WholeBone_nofibend(:,:,i)=Data_Whole_bin(:,:,i);
  80. if Distal == 1 % if we are processing a distal tibia => removed the wing (fibula disconnection at the ankle)
  81. slice = Data_WholeBone_nofibend(:,:,i);
  82. maxErosion = 8; % Max erosion iterations
  83. minAreaThreshold = 100; % Minimum size (pixels) to consider object real
  84. connected = true;
  85. radius = 1; % initialization
  86. while connected && radius <= maxErosion
  87. % Erode with increasing radius
  88. temp = imerode(slice, strel('disk', radius));
  89. % Label connected components
  90. cc = bwconncomp(temp, 8);
  91. % Measure component areas
  92. stats = regionprops(cc, 'Area');
  93. areas = [stats.Area];
  94. % Sort in descending order
  95. sortedAreas = sort(areas, 'descend');
  96. % Check if second-largest component is big enough
  97. if numel(sortedAreas) >= 2 && sortedAreas(2) > minAreaThreshold
  98. tibia = bwareafilt(temp, 1); % Keep largest object
  99. restored = imdilate(tibia, strel('disk', radius));
  100. Data_WholeBone_nofibend(:,:,i) = restored;
  101. % fprintf('Valid separation at slice %d with erosion radius %d\n', i, radius);
  102. connected = false;
  103. else
  104. radius = radius + 1; % increase radius if needed until we reach maxerosion parameter
  105. end
  106. end
  107. end
  108. % % Apply the nofibend mask to PreBone
  109. % PreBone_nofibend = zeros(size(PreBone), 'like', PreBone); % preallocate same type
  110. % PreBone_nofibend (Data_WholeBone_nofibend) = PreBone(Data_WholeBone_nofibend);
  111. % PreBone_nofibend =imbinarize(PreBone_nofibend);
  112. maskSlice = logical(Data_WholeBone_nofibend(:,:,i));
  113. PreBone_nofibend(:,:,i) = logical(PreBone(:,:,i)) & maskSlice;
  114. %
  115. I{i}=Data_WholeBone_nofibend(:,:,i); % we want to use whole bone to capture marrow space and outer surface
  116. I_closed{i}=imclose(I{i},strel('Disk',4)); % close cortical bone to avoid blood vessel/gaps
  117. I_inv{i}=~I_closed{i}; % invert the image (black -> white)
  118. % threshArea=100000;% only keep Marrow area and remove background (large white bloc)
  119. regions=regionprops(I_inv{i});
  120. regions_area_mat=[regions.Area];
  121. threshArea=max(regions_area_mat)-1;% define threshold to remove brackground
  122. Ma{i}= xor(I_inv{i}, bwareaopen(I_inv{i} , threshArea)); % Define Marrow space
  123. Ma_dilated{i}=imdilate(Ma{i},se_4); % dilated Marrow space
  124. I_fill{i}=I{i}+Ma_dilated{i}; % create a mask with cortical bone and fileld bone marrow space
  125. temp=I_fill{i};
  126. I_fill{i}=imclose(temp,strel('Disk',4)); % close any remaining gap
  127. %
  128. Ma_sumPixel{i}=sum(Ma{i},"all"); %if the masking did not work because to large of a gap in the bone (blodd vessel) => Ma{i} does not have any pixel
  129. if i>1 & i<5
  130. if Ma_sumPixel{i}==0 | Ma_sumPixel{i}<0.25*Ma_sumPixel{1}% if no pixel in the image or if MA{i} smaller than expected compared to previous slide
  131. I_closed{i}=imclose(I{i},strel('Disk',6)); % use large radius to close the bone
  132. I_inv{i}=~I_closed{i};
  133. regions=regionprops(I_inv{i});
  134. regions_area_mat=[regions.Area];
  135. threshArea=max(regions_area_mat)-1;% define threshold to remove brackground
  136. Ma{i}= xor(I_inv{i}, bwareaopen(I_inv{i} , threshArea));
  137. Area_Ma{i}=bwarea(Ma{i});
  138. Area_Ma_mat=cell2mat(Area_Ma);
  139. Ma_dilated{i}=imdilate(Ma{i},strel('Disk',4));
  140. I_fill{i}=I{i}+Ma{i};
  141. I_fill{i}=imclose(I_fill{i},strel('Disk',4));
  142. end
  143. end
  144. radius_close=7; % define radius to close the bone gap
  145. closed=0; %condition to remove of the while loop
  146. if i>=5
  147. % I_fill_sumPixel=sum(I_fill{i-3},"all"); %get the sum of pixel from the whole bone area from 4 slices away
  148. if Ma_sumPixel{i}==0 | Ma_sumPixel{i}<0.50*Ma_sumPixel{i-3} % if the sum of pixel is inferior to 80% of the image located 3 slices away
  149. I_closed{i}=imclose(I{i},strel('Disk',radius_close)); % then use a larger radius to close the bone
  150. I_inv{i}=~I_closed{i};
  151. regions=regionprops(I_inv{i});
  152. regions_area_mat=[regions.Area];
  153. threshArea=max(regions_area_mat)-1;% define threshold to remove brackground
  154. Ma{i}= xor(I_inv{i}, bwareaopen(I_inv{i} , threshArea));
  155. Area_Ma{i}=bwarea(Ma{i});
  156. Area_Ma_mat=cell2mat(Area_Ma);
  157. Ma_dilated{i}=imdilate(Ma{i},strel('Disk',4));
  158. I_fill{i}=I{i}+Ma{i};
  159. I_fill{i}=imclose(I_fill{i},strel('Disk',4)); % close cortical bone to avoid blood vessel/gaps
  160. check{i}=1;
  161. end
  162. end
  163. % Remove thin connections (e.g., 1-pixel width)
  164. I_cleaned = bwareaopen(I_fill{i}, 10); % removes small blobs
  165. I_cleaned = imerode(I_cleaned, strel('square', 2)); % erode to break connections
  166. I_cleaned = imdilate(I_cleaned, strel('square', 2)); % restore shape
  167. % Reassign cleaned image
  168. I_fill{i} = I_cleaned;
  169. % Get all regions
  170. regions_TibiaFib = regionprops("table", I_fill{i}, "Area", "PixelIdxList");
  171. % Find the largest region
  172. [~, idxLargest] = max(regions_TibiaFib.Area);
  173. % Create a blank mask
  174. I_largest = false(size(I_fill{i}));
  175. % Fill in only the largest region
  176. I_largest(regions_TibiaFib.PixelIdxList{idxLargest}) = true;
  177. % Overwrite or store result
  178. I_fill{i} = I_largest;
  179. I_intersect{i} = I_fill{i} & Ma{i};
  180. Ma{i}=I_intersect{i};
  181. Marrow_3d(:,:,i) = logical(Ma{i});
  182. PreBoneFill_3d(:,:,i) = imfill(logical(PreBone_nofibend(:,:,i)),'holes');
  183. Outer_perim(:,:,i)=bwperim(I_fill{i}); % Outer perimeter is the outer perimeter of the bone
  184. Outer_perim_dilated(:,:,i)=imdilate(Outer_perim(:,:,i),se_6);
  185. Inner_perim(:,:,i)=bwperim(Ma{i}); % Inner perimeter is the outer perimeter of the marrow space
  186. Inner_perim_dilated(:,:,i)=imdilate(Inner_perim(:,:,i),se_6);
  187. stats_Outer_perim = regionprops(I_fill{i}, 'Perimeter'); % calculate perimeter
  188. % Sum all perimeters for this slice (handles multiple regions, in case
  189. % there the bone marrow space is split
  190. total_outer_perim_px(i) = sum([stats_Outer_perim.Perimeter]);
  191. Outer_perimeter_length_px(i) = total_outer_perim_px(i);
  192. Outer_perimeter_length_um(i) = total_outer_perim_px(i) * pixel_xy_um; % convert to microns
  193. stats_Inner_perim = regionprops(Ma{i}, 'Perimeter');% calculate perimeter
  194. % Sum all perimeters for this slice (handles multiple regions)
  195. total_inner_perim_px(i) = sum([stats_Inner_perim.Perimeter]);
  196. Inner_perimeter_length_px(i) = total_inner_perim_px(i);
  197. Inner_perimeter_length_um(i) = total_inner_perim_px(i) * pixel_xy_um;
  198. total_all_perim_px(i)=total_outer_perim_px(i) + total_inner_perim_px(i); % perimeter of endo + perio
  199. end
  200. sx = pixel_xy_um; sy = pixel_xy_um; sz = pixel_z_um;
  201. S_endo_um2 = surfaceArea_um2_fromMask(Marrow_3d, sx, sy, sz); % endosteal
  202. S_perio_um2 = surfaceArea_um2_fromMask(PreBoneFill_3d, sx, sy, sz); % periosteal
  203. S_endo_mm2 = S_endo_um2 / 1e6;
  204. S_perio_mm2 = S_perio_um2 / 1e6;
  205. fprintf('Endosteal surface = %.3f um^2\n', S_endo_um2);
  206. fprintf('Periosteal surface = %.3f um^2\n', S_perio_um2);
  207. % APPLY MASKS — CONNECTED-COMPONENT EXPANSION
  208. % - initialCapture = Formation_bin & DilatedPerim
  209. % - keep any connected component of Formation that intersects initialCapture
  210. % This extends the captured periosteal/endosteal regions to include bumps
  211. % that are connected to the captured region.
  212. % - Any connected component captured in INNER is not allowed in OUTER.
  213. j = 0;
  214. min_keep_pixels = 3;
  215. for i = start:stop
  216. % binarize formation/resorption slices
  217. Data_Formation_bin = imbinarize(Data_Formation(:,:,i));
  218. Data_Resorption_bin = imbinarize(Data_Resorption(:,:,i));
  219. % ===============================
  220. % 1) ---- INNER (ENDO) PROCESSING
  221. % ===============================
  222. IP_dil = Inner_perim_dilated(:,:,i) > 0;
  223. % INNER formation initial capture
  224. initialCapture_inner = Data_Formation_bin & IP_dil;
  225. % Connected components of formation
  226. CC_F_inner = bwconncomp(Data_Formation_bin);
  227. finalCapture_inner = false(size(Data_Formation_bin));
  228. for k = 1:CC_F_inner.NumObjects
  229. pix = CC_F_inner.PixelIdxList{k};
  230. if any(initialCapture_inner(pix))
  231. finalCapture_inner(pix) = true;
  232. end
  233. end
  234. finalCapture_inner = bwareaopen(finalCapture_inner, min_keep_pixels);
  235. % INNER resorption
  236. initialCapture_inner_R = Data_Resorption_bin & IP_dil;
  237. CC_R_inner = bwconncomp(Data_Resorption_bin);
  238. finalCapture_inner_R = false(size(Data_Resorption_bin));
  239. for k = 1:CC_R_inner.NumObjects
  240. pix = CC_R_inner.PixelIdxList{k};
  241. if any(initialCapture_inner_R(pix))
  242. finalCapture_inner_R(pix) = true;
  243. end
  244. end
  245. finalCapture_inner_R = bwareaopen(finalCapture_inner_R, min_keep_pixels);
  246. % Save inner masks
  247. Inner_Formation(:,:,i) = finalCapture_inner;
  248. Inner_Resorption(:,:,i) = finalCapture_inner_R;
  249. % -------------------------------------------------------------
  250. % Create a mask of components already assigned to INNER
  251. % so we can exclude them from OUTER counts
  252. % -------------------------------------------------------------
  253. INNER_assigned_mask = finalCapture_inner | finalCapture_inner_R;
  254. % ===============================
  255. % 2) ---- OUTER (PERIO) PROCESSING
  256. % ===============================
  257. OP_dil = Outer_perim_dilated(:,:,i) > 0;
  258. % OUTER formation initial capture
  259. initialCapture_outer = Data_Formation_bin & OP_dil;
  260. CC_F_outer = bwconncomp(Data_Formation_bin);
  261. finalCapture_outer = false(size(Data_Formation_bin));
  262. for k = 1:CC_F_outer.NumObjects
  263. pix = CC_F_outer.PixelIdxList{k};
  264. % SKIP if this component belongs to INNER
  265. if any(INNER_assigned_mask(pix))
  266. continue
  267. end
  268. % otherwise include if it touches outer
  269. if any(initialCapture_outer(pix))
  270. finalCapture_outer(pix) = true;
  271. end
  272. end
  273. finalCapture_outer = bwareaopen(finalCapture_outer, min_keep_pixels);
  274. % OUTER resorption
  275. initialCapture_outer_R = Data_Resorption_bin & OP_dil;
  276. CC_R_outer = bwconncomp(Data_Resorption_bin);
  277. finalCapture_outer_R = false(size(Data_Resorption_bin));
  278. for k = 1:CC_R_outer.NumObjects
  279. pix = CC_R_outer.PixelIdxList{k};
  280. % SKIP if already counted in INNER
  281. if any(INNER_assigned_mask(pix))
  282. continue
  283. end
  284. if any(initialCapture_outer_R(pix))
  285. finalCapture_outer_R(pix) = true;
  286. end
  287. end
  288. finalCapture_outer_R = bwareaopen(finalCapture_outer_R, min_keep_pixels);
  289. % Save outer masks
  290. Outer_Formation(:,:,i) = finalCapture_outer;
  291. Outer_Resorption(:,:,i) = finalCapture_outer_R;
  292. % ===============================
  293. % 3) ---- METRICS
  294. % ===============================
  295. j = j + 1;
  296. sum_pix_formation_inner(j) = sum(finalCapture_inner(:));
  297. sum_pix_formation_outer(j) = sum(finalCapture_outer(:));
  298. sum_pix_resorption_inner(j) = sum(finalCapture_inner_R(:));
  299. sum_pix_resorption_outer(j) = sum(finalCapture_outer_R(:));
  300. sum_pix_prebone(j) = sum(PreBone_nofibend(:,:,i), "all") / 255;
  301. end
  302. % Plot
  303. x=linspace(1,stop,stop);
  304. fig1=figure(1);
  305. hold on
  306. plot(sum_pix_formation_outer,x,'b',LineWidth=2);
  307. plot(sum_pix_formation_inner,x,'C',LineWidth=2);
  308. ylabel('Slice number (0 <- Distal Proximal -> 100)');
  309. xlabel('Number of pixel per slice');
  310. legend('Periosteum', 'Endosteum');
  311. title('Formation - Tibia distal end');
  312. fig2=figure(2);
  313. hold on
  314. plot(sum_pix_resorption_outer,x,'r',LineWidth=2);
  315. plot(sum_pix_resorption_inner,x,'m',LineWidth=2);
  316. ylabel('Slice number (0 <- Distal Proximal -> 100)');
  317. xlabel('Number of pixel per slice');
  318. legend('Periosteum', 'Endosteum');
  319. title('Resorption - Tibia distal end');
  320. % fig3=figure(3);
  321. % imshow(Data_WholeBone_nofibend(:,:,5));
  322. %
  323. % fig4=figure(4);
  324. % imshow(Data_WholeBone_nofibend(:,:,15));
  325. %
  326. % fig5=figure(5);
  327. % imshow(Data_WholeBone_nofibend(:,:,25));
  328. fig6=figure(6);
  329. plot(Inner_perimeter_length_um,x);
  330. ylabel('Slice number (0 <- Distal Proximal -> 100)');
  331. xlabel('Endosteal Perimeter length (um)');
  332. title('Endosteal Perimeter length ');
  333. xlim([0, max(Inner_perimeter_length_um)+500]);
  334. fig7=figure(7);
  335. plot(Outer_perimeter_length_um,x);
  336. ylabel('Slice number (0 <- Distal Proximal -> 100)');
  337. xlabel('Periosteal Perimeter length (um)');
  338. title(' Periosteal Perimeter length ');
  339. xlim([0, max(Outer_perimeter_length_um+500)]);
  340. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  341. for i=start:stop
  342. if Prox==1 % if proximal end of the tibia : remove fibula from wholeBone data set
  343. WB_filled=imfill(Outer_perim_dilated(:,:,i));
  344. WB_noFib=WB_filled & Data_WholeBone(:,:,i);
  345. WB_PreBone_noFib(:,:,i)=WB_filled & PreBone(:,:,i);
  346. % cortical_3d(:,:,i)=WB_noFib;
  347. cortical_3d(:,:,i)=WB_PreBone_noFib(:,:,i);
  348. if Trab_only==1 % trabecular bone comportement isolation
  349. se_test=strel('Disk',8);
  350. Inner_perim_close{i}=imclose(Inner_perim(:,:,i),se_test);
  351. Inner_perim_close_fill{i}=imfill(Inner_perim_close{i},'holes');
  352. cortical{i}=I_fill{i}-Inner_perim_close_fill{i};
  353. cortical_bw{i}=imbinarize(cortical{i});
  354. I_nofib{i}=I{i} & I_fill {i};
  355. trab{i}=I_nofib{i}-cortical{i};
  356. trab_bw{i}=imbinarize(trab{i});
  357. trab_area{i}=bwarea(trab_bw{i});
  358. trab_3d(:,:,i)=trab_bw{i};
  359. end
  360. end
  361. if Mid==1
  362. % cortical_3d(:,:,i)=Data_WholeBone(:,:,i)/255;
  363. cortical_3d(:,:,i)=PreBone_nofibend(:,:,i);
  364. end
  365. if Distal==1
  366. % cortical_3d(:,:,i)=Data_WholeBone(:,:,i)/255;
  367. cortical_3d(:,:,i)=PreBone_nofibend(:,:,i);
  368. end
  369. end
  370. if Trab_only==1 % if trab segmentation was needed -> check resulst
  371. im_Check=100;
  372. figure(8)
  373. trab_overlay=imoverlay(Data_WholeBone(:,:,im_Check),trab_bw{im_Check},'red');
  374. imshow(trab_overlay)
  375. title('Trabecular bone segmentation (red)')
  376. end
  377. %-------------------------------
  378. % AUTOMATIC ENDO / PERIO / BOTH
  379. %-------------------------------
  380. % Modes definition: {ModeName, Endo_only, Perio_only, Endo_Perio}
  381. modes = {
  382. 'Endo', 1, 0, 0
  383. 'Perio', 0, 1, 0
  384. 'EndoPerio', 0, 0, 1};
  385. se2 = strel('Disk',2); % structuring element for dilation
  386. se3 = strel('Disk',3); % structuring element for dilation
  387. % -------------------------------
  388. % STORE RESULTS PER MODE (so nothing gets overwritten)
  389. % -------------------------------
  390. results = struct();
  391. results(1).modeName = 'Endo';
  392. results(2).modeName = 'Perio';
  393. results(3).modeName = 'EndoPerio';
  394. for m = 1:size(modes,1)
  395. Endo_only = modes{m,2};
  396. Perio_only = modes{m,3};
  397. Endo_Perio = modes{m,4};
  398. modeName = modes{m,1};
  399. fprintf('\n==== Running mode: %s (Sample %s) ====\n', modeName, Folder);
  400. Outer_percent_forming_surf_px = nan(1, stop);
  401. Outer_percent_Resor_surf_px = nan(1, stop);
  402. Inner_percent_forming_surf_px = nan(1, stop);
  403. Inner_percent_Resor_surf_px = nan(1, stop);
  404. all_percent_forming_surf_px = nan(1, stop);
  405. all_percent_Resor_surf_px = nan(1, stop);
  406. % -------------------------------
  407. % Initialize masks
  408. % -------------------------------
  409. Formation_Cort_3d = false(size(cortical_3d));
  410. Resorption_Cort_3d = false(size(cortical_3d));
  411. Formation_dilated = false(size(cortical_3d));
  412. Resorption_dilated = false(size(cortical_3d));
  413. contact_InnerPerim_Form = false(size(cortical_3d));
  414. contact_InnerPerim_Resor = false(size(cortical_3d));
  415. contact_OuterPerim_Form = false(size(cortical_3d));
  416. contact_OuterPerim_Resor = false(size(cortical_3d));
  417. contact_allPerim_Form = false(size(cortical_3d));
  418. contact_allPerim_Resor = false(size(cortical_3d));
  419. forming_inner_perim_px = nan(size(total_inner_perim_px));
  420. Resor_inner_perim_px = nan(size(total_inner_perim_px));
  421. forming_Outer_perim_px = nan(size(total_outer_perim_px));
  422. Resor_Outer_perim_px = nan(size(total_outer_perim_px));
  423. forming_all_perim_px = nan(size(total_all_perim_px));
  424. Resor_all_perim_px = nan(size(total_all_perim_px));
  425. % -------------------------------
  426. % Create masks (keep _bw for QC / later use)
  427. % -------------------------------
  428. for i = start:stop
  429. if Endo_only
  430. Data_Resorption_bw{i} = cortical_3d(:,:,i) & Inner_Resorption(:,:,i);
  431. Resorption_Cort_3d(:,:,i) = bwmorph(Inner_Resorption(:,:,i),'clean');
  432. Data_Formation_bw{i} = cortical_3d(:,:,i) & Inner_Formation(:,:,i);
  433. Formation_Cort_3d(:,:,i) = bwmorph(Inner_Formation(:,:,i),'clean');
  434. elseif Perio_only
  435. Data_Resorption_bw{i} = cortical_3d(:,:,i) & Outer_Resorption(:,:,i);
  436. Resorption_Cort_3d(:,:,i) = bwmorph(Outer_Resorption(:,:,i),'clean');
  437. Data_Formation_bw{i} = cortical_3d(:,:,i) & Outer_Formation(:,:,i);
  438. Formation_Cort_3d(:,:,i) = bwmorph(Outer_Formation(:,:,i),'clean');
  439. elseif Endo_Perio
  440. Data_Resorption_bw{i} = cortical_3d(:,:,i) & (Outer_Resorption(:,:,i) | Inner_Resorption(:,:,i));
  441. Resorption_Cort_3d(:,:,i) = Outer_Resorption(:,:,i) | Inner_Resorption(:,:,i);
  442. Data_Formation_bw{i} = cortical_3d(:,:,i) & (Outer_Formation(:,:,i) | Inner_Formation(:,:,i));
  443. Formation_Cort_3d(:,:,i) = Outer_Formation(:,:,i) | Inner_Formation(:,:,i);
  444. end
  445. end
  446. % -------------------------------
  447. % QC visualization
  448. % -------------------------------
  449. s = 50; if Trab_only==1, s=100; end
  450. if Endo_only
  451. F_bin = Formation_Cort_3d(:,:,s); R_bin = Resorption_Cort_3d(:,:,s); modeStr = 'Endosteal (Endo)';
  452. elseif Perio_only
  453. F_bin = Formation_Cort_3d(:,:,s); R_bin = Resorption_Cort_3d(:,:,s); modeStr = 'Periosteal (Perio)';
  454. else
  455. F_bin = Formation_Cort_3d(:,:,s); R_bin = Resorption_Cort_3d(:,:,s); modeStr = 'Endo+Perio (Both)';
  456. end
  457. % Formation QC overlay
  458. F_orig_bin = imbinarize(Data_Formation(:,:,s));
  459. F_captured = F_orig_bin & F_bin;
  460. F_missed = F_orig_bin & ~F_bin;
  461. F_added = ~F_orig_bin & F_bin;
  462. RGB_F = zeros([size(F_orig_bin),3]);
  463. RGB_F(:,:,1) = F_missed; RGB_F(:,:,2) = F_captured; RGB_F(:,:,3) = F_added;
  464. figure; imshow(RGB_F); title(['Formation QC: ' modeStr ' - Green: Included for analysis; Red : Excluded']);
  465. % figure; imshow(F_orig_bin); hold on; contour(F_bin,[0.5 0.5],'y','LineWidth',1.5); title(['Formation Contour: ' modeStr]);
  466. % Resorption QC overlay
  467. R_orig_bin = imbinarize(Data_Resorption(:,:,s));
  468. R_captured = R_orig_bin & R_bin;
  469. R_missed = R_orig_bin & ~R_bin;
  470. R_added = ~R_orig_bin & R_bin;
  471. RGB_R = zeros([size(R_orig_bin),3]);
  472. RGB_R(:,:,1) = R_missed; RGB_R(:,:,2) = R_captured; RGB_R(:,:,3) = R_added;
  473. figure; imshow(RGB_R); title(['Resorption QC: ' modeStr ' - Green: Included for analysis; Red : Excluded']);
  474. % figure; imshow(R_orig_bin); hold on; contour(R_bin,[0.5 0.5],'m','LineWidth',1.5); title(['Resorption Contour: ' modeStr]);
  475. % -------------------------------
  476. % Overlay: Prebone - Formation - Resorption
  477. % -------------------------------
  478. s2 = 20; if Prox==1, s2=100; end
  479. slice1 = double(cortical_3d(:,:,s2));
  480. slice_formation = double(Formation_Cort_3d(:,:,s2));
  481. slice_resorption = double(Resorption_Cort_3d(:,:,s2));
  482. background_only = double(slice1 & ~slice_formation & ~slice_resorption);
  483. overlay1 = cat(3,0.8*background_only + slice_resorption, 0.8*background_only, 0.8*background_only + slice_resorption);
  484. overlay1 = min(overlay1,1); figure; imshow(overlay1); title([modeStr ': Resorption only (magenta)' ]);
  485. overlay2 = cat(3,0.8*background_only, 0.8*background_only + slice_formation, 0.8*background_only + slice_formation);
  486. overlay2 = min(overlay2,1); figure; imshow(overlay2); title([modeStr ': Formation only (cyan)']);
  487. overlay3 = cat(3,0.8*background_only + slice_resorption, 0.8*background_only + slice_formation, 0.8*background_only + slice_formation + slice_resorption);
  488. overlay3 = min(overlay3,1); figure; imshow(overlay3); title([modeStr ': Formation (cyan) & Resorption (magenta)']);
  489. % -------------------------------
  490. % MS / ES calculation (normalized by perimeter)
  491. % -------------------------------
  492. %Preallocate (recommended)
  493. Inner_percent_forming_surf_px = nan(1, size(Inner_perim,3));
  494. Inner_percent_Resor_surf_px = nan(1, size(Inner_perim,3));
  495. Outer_percent_forming_surf_px = nan(1, size(Outer_perim,3));
  496. Outer_percent_Resor_surf_px = nan(1, size(Outer_perim,3));
  497. all_percent_forming_surf_px = nan(1, size(Outer_perim,3));
  498. all_percent_Resor_surf_px = nan(1, size(Outer_perim,3));
  499. if Endo_only
  500. for i = start:stop
  501. % ensure logical
  502. Inner_perim(:,:,i) = logical(Inner_perim(:,:,i));
  503. Formation_dilated(:,:,i) = imdilate(logical(Formation_Cort_3d(:,:,i)), se_3_sqr);
  504. Resorption_dilated(:,:,i)= imdilate(logical(Resorption_Cort_3d(:,:,i)), se_3_sqr);
  505. % contacts
  506. contact_InnerPerim_Form(:,:,i) = Inner_perim(:,:,i) & Formation_dilated(:,:,i);
  507. contact_InnerPerim_Resor(:,:,i) = Inner_perim(:,:,i) & Resorption_dilated(:,:,i);
  508. % PIXEL perimeter denominator
  509. den = nnz(Inner_perim(:,:,i));
  510. if den > 0
  511. Inner_percent_forming_surf_px(i) = 100 * nnz(contact_InnerPerim_Form(:,:,i)) / den;
  512. Inner_percent_Resor_surf_px(i) = 100 * nnz(contact_InnerPerim_Resor(:,:,i)) / den;
  513. end
  514. end
  515. nVox_Formation_endo = sum(Inner_Formation(:),'all');
  516. FormationVolume_um3_endo = nVox_Formation_endo * voxelVolume_um3;
  517. nVox_Resorption_endo = sum(Inner_Resorption(:),'all');
  518. ResorptionVolume_um3_endo = nVox_Resorption_endo * voxelVolume_um3;
  519. PreBone_BV=(sum(cortical_3d,"all"))*voxelVolume_um3;
  520. MS_avg_Innerperim = mean(Inner_percent_forming_surf_px(start:stop), 'omitnan'); %Endosteal mineralizing surface
  521. ES_avg_Innerperim = mean(Inner_percent_Resor_surf_px(start:stop), 'omitnan'); %Endosteal eroding surface
  522. BFR_avg_Innerperim=100*FormationVolume_um3_endo/(days*PreBone_BV); % formed bone volume on the endosteum/ total bone volume /day (BFR.BV)
  523. BRR_avg_Innerperim=100*ResorptionVolume_um3_endo/(days*PreBone_BV); % formed bone volume on the endosteum/ total bone volume /day (BFR.BV)
  524. BFR_BS_endo = FormationVolume_um3_endo / (days * S_endo_um2);
  525. BRR_BS_endo = ResorptionVolume_um3_endo / (days * S_endo_um2);
  526. %show result in the command window for manual recording
  527. fprintf('\nResults - Endosteal Surface\n');
  528. fprintf('ENDO.MS = %.4f (%%)\n', MS_avg_Innerperim);
  529. fprintf('ENDO.ES = %.4f (%%)\n', ES_avg_Innerperim);
  530. fprintf('ENDO.BFR/BV = %.4f (%%/day)\n', BFR_avg_Innerperim);
  531. fprintf('ENDO.BRR/BV = %.4f (%%/day)\n', BRR_avg_Innerperim);
  532. fprintf('ENDO.BFR/BS = %.4f (um3/um2/d)\n', BFR_BS_endo);
  533. fprintf('ENDO.BRR/BS = %.4f (um3/um2/d)\n', BRR_BS_endo);
  534. end
  535. if Perio_only
  536. for i = start:stop
  537. % ensure logical
  538. Outer_perim(:,:,i) = logical(Outer_perim(:,:,i));
  539. % Formation_dilated(:,:,i) = imdilate(logical(Formation_Cort_3d(:,:,i)), se_0);
  540. Formation_dilated(:,:,i) = imdilate(logical(Formation_Cort_3d(:,:,i)),se_3_sqr);
  541. Resorption_dilated(:,:,i)= imdilate(logical(Resorption_Cort_3d(:,:,i)), se_3_sqr);
  542. % contacts
  543. contact_OuterPerim_Form(:,:,i) = Outer_perim(:,:,i) & Formation_dilated(:,:,i);
  544. contact_OuterPerim_Resor(:,:,i) = Outer_perim(:,:,i) & Resorption_dilated(:,:,i);
  545. % PIXEL perimeter denominator
  546. den = nnz(Outer_perim(:,:,i));
  547. if den > 0
  548. Outer_percent_forming_surf_px(i) = 100 * nnz(contact_OuterPerim_Form(:,:,i)) / den;
  549. Outer_percent_Resor_surf_px(i) = 100 * nnz(contact_OuterPerim_Resor(:,:,i)) / den;
  550. end
  551. end
  552. nVox_Formation_perio = sum(Outer_Formation(:),'all'); %calculate the of volume of bone formation of the perisoteal surface
  553. FormationVolume_um3_perio = nVox_Formation_perio * voxelVolume_um3;
  554. nVox_Resorption_perio = sum(Outer_Resorption(:),'all');
  555. ResorptionVolume_um3_perio = nVox_Resorption_perio * voxelVolume_um3;
  556. PreBone_BV=(sum(cortical_3d,"all"))*voxelVolume_um3;
  557. MS_avg_Outerperim = mean(Outer_percent_forming_surf_px(start:stop), 'omitnan');
  558. ES_avg_Outerperim = mean(Outer_percent_Resor_surf_px(start:stop), 'omitnan');
  559. BFR_avg_Outerperim=100*FormationVolume_um3_perio/(days*PreBone_BV); % formed bone volume on the endosteum/ total bone volume /day
  560. BRR_avg_Outerperim=100*ResorptionVolume_um3_perio/(days*PreBone_BV); % formed bone volume on the endosteum/ total bone volume /day
  561. BFR_BS_perio = FormationVolume_um3_perio / (days * S_perio_um2);
  562. BRR_BS_perio = ResorptionVolume_um3_perio / (days * S_perio_um2);
  563. %show result in the command window for manual recording
  564. fprintf('\nResults - Periosteal Surface\n');
  565. fprintf('Perio.MS = %.4f (%%)\n', MS_avg_Outerperim);
  566. fprintf('Perio.ES = %.4f (%%)\n', ES_avg_Outerperim);
  567. fprintf('Perio.BFR/BV = %.4f (%%/day)\n', BFR_avg_Outerperim);
  568. fprintf('Perio.BRR/BV = %.4f (%%/day)\n', BRR_avg_Outerperim);
  569. fprintf('Perio.BFR/BS = %.4f (um3/um2/d)\n', BFR_BS_perio);
  570. fprintf('Perio.BRR/BS = %.4f (um3/um2/d)\n', BRR_BS_perio);
  571. end
  572. if Endo_Perio
  573. for i = start:stop
  574. % ensure logical
  575. Outer_perim(:,:,i) = logical(Outer_perim(:,:,i));
  576. Inner_perim(:,:,i) = logical(Inner_perim(:,:,i));
  577. Formation_dilated(:,:,i) = imdilate(logical(Formation_Cort_3d(:,:,i)), se_3_sqr);
  578. Resorption_dilated(:,:,i)= imdilate(logical(Resorption_Cort_3d(:,:,i)), se_3_sqr);
  579. all_perim(:,:,i) = Outer_perim(:,:,i) | Inner_perim(:,:,i);
  580. % contacts
  581. contact_allPerim_Form(:,:,i) = all_perim(:,:,i) & Formation_dilated(:,:,i);
  582. contact_allPerim_Resor(:,:,i) = all_perim(:,:,i) & Resorption_dilated(:,:,i);
  583. % PIXEL perimeter denominator
  584. den = nnz(all_perim(:,:,i));
  585. if den > 0
  586. all_percent_forming_surf_px(i) = 100 * nnz(contact_allPerim_Form(:,:,i)) / den;
  587. all_percent_Resor_surf_px(i) = 100 * nnz(contact_allPerim_Resor(:,:,i)) / den;
  588. end
  589. end
  590. nVox_Formation_EndoPerio = sum(Formation_Cort_3d(:),'all');
  591. FormationVolume_um3_EndoPerio = nVox_Formation_EndoPerio * voxelVolume_um3;
  592. nVox_Resorption_EndoPerio = sum(Resorption_Cort_3d(:),'all');
  593. ResorptionVolume_um3_EndoPerio = nVox_Resorption_EndoPerio * voxelVolume_um3;
  594. PreBone_BV=(sum(cortical_3d,"all"))*voxelVolume_um3;
  595. MS_avg_allperim = mean(all_percent_forming_surf_px(start:stop), 'omitnan');
  596. ES_avg_allperim = mean(all_percent_Resor_surf_px(start:stop), 'omitnan');
  597. BFR_avg_allperim=100*FormationVolume_um3_EndoPerio/(days*PreBone_BV); % formed bone volume on the endosteum/ total bone volume /day
  598. BRR_avg_allperim=100*ResorptionVolume_um3_EndoPerio/(days*PreBone_BV); % formed bone volume on the endosteum/ total bone volume /day
  599. FormationVolume_um3_endo = sum(Inner_Formation(:),'all') * voxelVolume_um3;
  600. ResorptionVolume_um3_endo = sum(Inner_Resorption(:),'all') * voxelVolume_um3;
  601. FormationVolume_um3_perio = sum(Outer_Formation(:),'all') * voxelVolume_um3;
  602. ResorptionVolume_um3_perio = sum(Outer_Resorption(:),'all') * voxelVolume_um3;
  603. BFR_BS_endo = FormationVolume_um3_endo / (days * S_endo_um2);
  604. BRR_BS_endo = ResorptionVolume_um3_endo / (days * S_endo_um2);
  605. BFR_BS_perio = FormationVolume_um3_perio / (days * S_perio_um2);
  606. BRR_BS_perio = ResorptionVolume_um3_perio / (days * S_perio_um2);
  607. %show result in the command window for manual recording
  608. fprintf('\nResults - Endosteal + Perisoteal Surface\n');
  609. fprintf('EndoPerio.MS = %.4f (%%)\n', MS_avg_allperim);
  610. fprintf('EndoPerio.ES = %.4f (%%)\n', ES_avg_allperim);
  611. fprintf('EndoPerio.BFR/BV = %.4f (%%/day)\n', BFR_avg_allperim);
  612. fprintf('EndoPerio.BRR/BV = %.4f (%%/day)\n', BRR_avg_allperim);
  613. fprintf('EndoPerio.BFR/BS = %.4f (um3/um2/d)\n', BFR_BS_perio+BFR_BS_endo);
  614. fprintf('EndoPerio.BRR/BS = %.4f (um3/um2/d)\n', BRR_BS_perio+BRR_BS_endo);
  615. end
  616. % -------------------------------
  617. % SAVE EVERYTHING FOR THIS MODE
  618. % -------------------------------
  619. results(m).Endo_only = Endo_only;
  620. results(m).Perio_only = Perio_only;
  621. results(m).Endo_Perio = Endo_Perio;
  622. results(m).S_endo_um2 = S_endo_um2;
  623. results(m).S_perio_um2 = S_perio_um2;
  624. % Per-slice percentages
  625. results(m).Outer_percent_forming_surf_px = Outer_percent_forming_surf_px;
  626. results(m).Outer_percent_Resor_surf_px = Outer_percent_Resor_surf_px;
  627. results(m).Inner_percent_forming_surf_px = Inner_percent_forming_surf_px;
  628. results(m).Inner_percent_Resor_surf_px = Inner_percent_Resor_surf_px;
  629. results(m).all_percent_forming_surf_px = all_percent_forming_surf_px;
  630. results(m).all_percent_Resor_surf_px = all_percent_Resor_surf_px;
  631. % Per-slice contact pixel counts
  632. results(m).forming_inner_perim_px = forming_inner_perim_px;
  633. results(m).Resor_inner_perim_px = Resor_inner_perim_px;
  634. results(m).forming_Outer_perim_px = forming_Outer_perim_px;
  635. results(m).Resor_Outer_perim_px = Resor_Outer_perim_px;
  636. results(m).forming_all_perim_px = forming_all_perim_px;
  637. results(m).Resor_all_perim_px = Resor_all_perim_px;
  638. % Save MS/ES/BFR/BRR depending on mode
  639. if Endo_only
  640. results(m).MS = MS_avg_Innerperim;
  641. results(m).ES = ES_avg_Innerperim;
  642. results(m).BFR = BFR_avg_Innerperim;
  643. results(m).BRR = BRR_avg_Innerperim;
  644. results(m).BFR_BS_endo = BFR_BS_endo;
  645. results(m).BRR_BS_endo = BRR_BS_endo;
  646. elseif Perio_only
  647. results(m).MS = MS_avg_Outerperim;
  648. results(m).ES = ES_avg_Outerperim;
  649. results(m).BFR = BFR_avg_Outerperim;
  650. results(m).BRR = BRR_avg_Outerperim;
  651. results(m).BFR_BS_perio = BFR_BS_perio;
  652. results(m).BRR_BS_perio = BRR_BS_perio;
  653. elseif Endo_Perio
  654. results(m).MS = MS_avg_allperim;
  655. results(m).ES = ES_avg_allperim;
  656. results(m).BFR = BFR_avg_allperim;
  657. results(m).BRR = BRR_avg_allperim;
  658. results(m).BFR_BS_endo = BFR_BS_endo;
  659. results(m).BRR_BS_endo = BRR_BS_endo;
  660. results(m).BFR_BS_perio = BFR_BS_perio;
  661. results(m).BRR_BS_perio = BRR_BS_perio;
  662. end
  663. % (Optional) store masks for later QC / debugging
  664. results(m).Formation_Cort_3d = Formation_Cort_3d;
  665. results(m).Resorption_Cort_3d = Resorption_Cort_3d;
  666. results(m).contact_InnerPerim_Form = contact_InnerPerim_Form;
  667. results(m).contact_InnerPerim_Resor = contact_InnerPerim_Resor;
  668. results(m).contact_OuterPerim_Form = contact_OuterPerim_Form;
  669. results(m).contact_OuterPerim_Resor = contact_OuterPerim_Resor;
  670. results(m).contact_allPerim_Form = contact_allPerim_Form;
  671. results(m).contact_allPerim_Resor = contact_allPerim_Resor;
  672. % -------------------------------
  673. % Export metrics to Excel (safe copy)
  674. % -------------------------------
  675. Cort_excel = directory_excelfile;
  676. local_copy = fullfile(tempdir, 'results_3DHisto_local.xlsx');
  677. system('taskkill /F /IM EXCEL.EXE'); pause(0.5);
  678. copyfile(Cort_excel, local_copy, 'f');
  679. sample_num = regexp(Folder, '^\d+', 'match'); sample_num = string(sample_num{1});
  680. listSheet = "List_samples_groups";
  681. try
  682. group_table_raw = readcell(local_copy, 'Sheet', listSheet);
  683. catch
  684. headers = {'Group','SampleID'}; writecell(headers, local_copy, 'Sheet', listSheet, 'UseExcel', false);
  685. group_table_raw = readcell(local_copy, 'Sheet', listSheet);
  686. end
  687. all_groups = string(group_table_raw(2:end,1));
  688. all_samples = string(group_table_raw(2:end,2));
  689. idx = find(all_samples == sample_num);
  690. if isempty(idx), error("Sample %s not found in List_samples_groups!", sample_num); end
  691. group_name = all_groups(idx);
  692. sum_F = sum(Formation_Cort_3d,"all"); sum_R = sum(Resorption_Cort_3d,"all"); sum_PreBone = sum(cortical_3d,"all");
  693. F_BV = sum_F/sum_PreBone; R_BV = sum_R/sum_PreBone;
  694. % Choose sheet & measurement row based on flags
  695. if Prox==1 && Trab_only==1
  696. sheet = "Proxi_TrabOnly"; measure_row = {MS_avg_Innerperim, ES_avg_Innerperim, BFR_avg_Innerperim, BRR_avg_Innerperim, BFR_BS_endo, BRR_BS_endo};
  697. elseif Prox==1 && Perio_only==1
  698. sheet = "Proxi_Perio"; measure_row = {MS_avg_Outerperim, ES_avg_Outerperim, BFR_avg_Outerperim, BRR_avg_Outerperim, BFR_BS_perio, BRR_BS_perio};
  699. elseif Prox==1 && Endo_only==1
  700. sheet = "Proxi_Endo_CortTrab"; measure_row = {MS_avg_Innerperim, ES_avg_Innerperim, BFR_avg_Innerperim, BRR_avg_Innerperim, BFR_BS_endo, BRR_BS_endo};
  701. elseif Prox==1 && Endo_Perio==1
  702. sheet = "Proxi_EndoPerio"; measure_row = {MS_avg_allperim, ES_avg_allperim, BFR_avg_allperim, BRR_avg_allperim,BFR_BS_perio, BRR_BS_perio, BFR_BS_endo, BRR_BS_endo};
  703. elseif Mid==1 && Endo_only==1 && nowing==0
  704. sheet = "Mid_Endo"; measure_row = {MS_avg_Innerperim, ES_avg_Innerperim, BFR_avg_Innerperim, BRR_avg_Innerperim, BFR_BS_endo, BRR_BS_endo};
  705. elseif Mid==1 && Perio_only==1 && nowing==0
  706. sheet = "Mid_Perio"; measure_row = {MS_avg_Outerperim, ES_avg_Outerperim, BFR_avg_Outerperim, BRR_avg_Outerperim, BFR_BS_perio, BRR_BS_perio};
  707. elseif Mid==1 && Endo_Perio==1 && nowing==0
  708. sheet = "Mid_EndoPerio"; measure_row = {MS_avg_allperim, ES_avg_allperim, BFR_avg_allperim, BRR_avg_allperim,BFR_BS_perio, BRR_BS_perio, BFR_BS_endo, BRR_BS_endo};
  709. elseif Mid==1 && Perio_only==1 && nowing==1
  710. sheet = "Mid_Perio_NoWing"; measure_row = {MS_avg_Outerperim, ES_avg_Outerperim, BFR_avg_Outerperim, BRR_avg_Outerperim, BFR_BS_perio, BRR_BS_perio};
  711. elseif Mid==1 && Endo_only==1 && nowing==1
  712. sheet = "Mid_Endo_NoWing"; measure_row = {MS_avg_Innerperim, ES_avg_Innerperim, BFR_avg_Innerperim, BRR_avg_Innerperim, BFR_BS_endo, BRR_BS_endo};
  713. elseif Mid==1 && Endo_Perio==1 && nowing==1
  714. sheet = "Mid_EndoPerio_NoWing"; measure_row = {MS_avg_allperim, ES_avg_allperim, BFR_avg_allperim, BRR_avg_allperim, BFR_BS_perio, BRR_BS_perio, BFR_BS_endo, BRR_BS_endo};
  715. elseif Distal==1 && Endo_only==1 && nowing==0
  716. sheet = "Distal_Endo"; measure_row = {MS_avg_Innerperim, ES_avg_Innerperim, BFR_avg_Innerperim, BRR_avg_Innerperim, BFR_BS_endo, BRR_BS_endo };
  717. elseif Distal==1 && Perio_only==1 && nowing==0
  718. sheet = "Distal_Perio"; measure_row = {MS_avg_Outerperim, ES_avg_Outerperim, BFR_avg_Outerperim, BRR_avg_Outerperim, BFR_BS_perio, BRR_BS_perio};
  719. elseif Distal==1 && Endo_Perio==1 && nowing==0
  720. sheet = "Distal_EndoPerio"; measure_row = {MS_avg_allperim, ES_avg_allperim, BFR_avg_allperim, BRR_avg_allperim, BFR_BS_perio, BRR_BS_perio, BFR_BS_endo, BRR_BS_endo};
  721. else
  722. error("Unknown combination of flags.");
  723. end
  724. % defaults (avoid "undefined variable" depending on mode)
  725. if ~exist('BFR_BS_perio','var'), BFR_BS_perio = NaN; end
  726. if ~exist('BRR_BS_perio','var'), BRR_BS_perio = NaN; end
  727. if ~exist('BFR_BS_endo','var'), BFR_BS_endo = NaN; end
  728. if ~exist('BRR_BS_endo','var'), BRR_BS_endo = NaN; end
  729. new_row = {group_name, sample_num, sum_R, sum_F, sum_PreBone, F_BV, R_BV, measure_row{:}};
  730. % new_row = {group_name, sample_num, sum_R, sum_F, sum_PreBone, F_BV, R_BV, measure_row{:}};
  731. try
  732. raw = readcell(local_copy, 'Sheet', sheet);
  733. catch
  734. headers = {'Group','SampleID','Resorption','Formation','PreBone','F/BV','R/BV', ...
  735. 'MS','ES','BFR','BRR','BFR_BS','BRR_BS'};
  736. writecell(headers, local_copy, 'Sheet', sheet, 'UseExcel', false);
  737. raw = readcell(local_copy, 'Sheet', sheet);
  738. end
  739. headers = raw(1,:); data = raw(2:end,:);
  740. existing_sample_ids = string(data(:,2)); match_idx = find(existing_sample_ids == sample_num);
  741. if isempty(match_idx)
  742. data = [data; new_row]; fprintf('Added NEW sample %s to sheet %s\n', sample_num, sheet);
  743. else
  744. data(match_idx,:) = new_row; fprintf('Updated sample %s in sheet %s\n', sample_num, sheet);
  745. end
  746. writecell([headers; data], local_copy, 'Sheet', sheet, 'UseExcel', false);
  747. copyfile(local_copy, Cort_excel, 'f'); fprintf('✓ Excel file updated safely to Box without locks.\n');
  748. system('taskkill /F /IM EXCEL.EXE');
  749. end
  750. %%
  751. % % -------------------------------
  752. % % 3D Volume visualization
  753. % % -------------------------------
  754. % out = cortical_3d;
  755. % In_F = Formation_Cort_3d;
  756. % In_R = Resorption_Cort_3d;
  757. %
  758. % titleText = "3D rendering - " + modeStr + " - Formation (cyan) & Resorption (magenta) ";
  759. % viewerLabels2 = viewer3d(BackgroundColor="white",BackgroundGradient="off",CameraZoom=2);
  760. % uilabel(viewerLabels2.Parent, ...
  761. % 'Text',titleText, ...
  762. % 'FontSize',16, ...
  763. % 'FontWeight','bold', ...
  764. % 'HorizontalAlignment','center', ...
  765. % 'Position',[20 viewerLabels2.Parent.Position(4)-40 ...
  766. % viewerLabels2.Parent.Position(3)-40 30]);
  767. %
  768. % volshow(out,Parent=viewerLabels2, RenderingStyle="GradientOpacity", ...
  769. % Alphamap=linspace(0,0.3,256).^1.2, Colormap=repmat(linspace(0,1,256)',1,3), ...
  770. % OverlayData=In_F, OverlayAlpha=0.2, OverlayColormap=repmat([0 1 1],256,1));
  771. %
  772. % volshow(out,Parent=viewerLabels2, RenderingStyle="GradientOpacity", ...
  773. % Alphamap=linspace(0,0.3,256).^1.2, Colormap=repmat(linspace(0,1,256)',1,3), ...
  774. % OverlayData=In_R, OverlayAlpha=0.2, OverlayColormap=repmat([1 0 1],256,1));
  775. out = cortical_3d;
  776. In_F = Formation_Cort_3d;
  777. In_R = Resorption_Cort_3d;
  778. viewerLabels2 = viewer3d(BackgroundColor="white",BackgroundGradient="off",CameraZoom=2);
  779. % main greyscale bone volume (no overlay alpha needed)
  780. hBone = safeVolshow(viewerLabels2, out, ...
  781. 'RenderingStyle','GradientOpacity', ...
  782. 'Alphamap',linspace(0,0.3,256).^1.2, ...
  783. 'Colormap',repmat(linspace(0,1,256)',1,3));
  784. % formation overlay (cyan)
  785. hForm = safeVolshow(viewerLabels2, out, ...
  786. 'RenderingStyle','GradientOpacity', ...
  787. 'Alphamap',linspace(0,0.3,256).^1.2, ...
  788. 'Colormap',repmat(linspace(0,1,256)',1,3), ...
  789. 'OverlayData', In_F, ...
  790. 'OverlayColormap', repmat([0 1 1],256,1), ...
  791. 'OverlayAlpha', 0.2);
  792. % resorption overlay (magenta)
  793. hRes = safeVolshow(viewerLabels2, out, ...
  794. 'RenderingStyle','GradientOpacity', ...
  795. 'Alphamap',linspace(0,0.3,256).^1.2, ...
  796. 'Colormap',repmat(linspace(0,1,256)',1,3), ...
  797. 'OverlayData', In_R, ...
  798. 'OverlayColormap', repmat([1 0 1],256,1), ...
  799. 'OverlayAlpha', 0.2);
  800. %%
  801. %%
  802. i = 38; % slice index
  803. Surface = 'Perio'; % 'Endo' or 'Perio'
  804. Mask = 'F'; % 'F' = Formation | 'R' = Resorption
  805. if strcmpi(Surface,'Endo')
  806. perim = logical(Inner_perim(:,:,i));
  807. if strcmpi(Mask,'F')
  808. mask_orig = logical(Inner_Formation(:,:,i));
  809. contact = logical(results(1).contact_InnerPerim_Form(:,:,i));
  810. maskName = 'Formation';
  811. elseif strcmpi(Mask,'R')
  812. mask_orig = logical(Inner_Resorption(:,:,i));
  813. contact = logical(results(1).contact_InnerPerim_Resor(:,:,i));
  814. maskName = 'Resorption';
  815. else
  816. error('Mask must be ''F'' or ''R''.');
  817. end
  818. perimName = 'Inner perim';
  819. elseif strcmpi(Surface,'Perio')
  820. perim = logical(Outer_perim(:,:,i));
  821. if strcmpi(Mask,'F')
  822. mask_orig = logical(Outer_Formation(:,:,i));
  823. contact = logical(results(2).contact_OuterPerim_Form(:,:,i));
  824. maskName = 'Formation';
  825. elseif strcmpi(Mask,'R')
  826. mask_orig = logical(Outer_Resorption(:,:,i));
  827. contact = logical(results(2).contact_OuterPerim_Resor(:,:,i));
  828. maskName = 'Resorption';
  829. else
  830. error('Mask must be ''F'' or ''R''.');
  831. end
  832. perimName = 'Outer perim';
  833. else
  834. error('Surface must be ''Endo'' or ''Perio''.');
  835. end
  836. % Initialize RGB
  837. RGB = zeros([size(perim) 3]);
  838. % -------------------------
  839. % Formation / Resorption mask: WHITE
  840. % -------------------------
  841. RGB(:,:,1) = mask_orig;
  842. RGB(:,:,2) = mask_orig;
  843. RGB(:,:,3) = mask_orig;
  844. % -------------------------
  845. % Perimeter: RED
  846. % -------------------------
  847. RGB(:,:,1) = RGB(:,:,1) | perim;
  848. % -------------------------
  849. % Contact: GREEN (override)
  850. % -------------------------
  851. RGB(:,:,2) = RGB(:,:,2) | contact; % ✅ add green, don't overwrite
  852. RGB(:,:,1) = RGB(:,:,1) & ~contact; % remove red under contact
  853. RGB(:,:,3) = RGB(:,:,3) & ~contact; % remove blue under contact
  854. % Display
  855. figure;
  856. imshow(RGB);
  857. title(sprintf('%s (red) | %s (white) | Contact (green) – slice %d', ...
  858. perimName, maskName, i));
  859. %%
  860. function S_um2 = surfaceArea_um2_fromMask(BW, sx, sy, sz)
  861. BW = logical(BW);
  862. if nnz(BW) == 0
  863. S_um2 = NaN;
  864. warning('surfaceArea_um2_fromMask: mask is empty -> returning NaN');
  865. return;
  866. end
  867. [F,V] = isosurface(BW, 0.5);
  868. % scale vertices into real units (µm)
  869. V(:,1) = V(:,1) * sx;
  870. V(:,2) = V(:,2) * sy;
  871. V(:,3) = V(:,3) * sz;
  872. % surface area (µm^2)
  873. p1 = V(F(:,1),:);
  874. p2 = V(F(:,2),:);
  875. p3 = V(F(:,3),:);
  876. S_um2 = 0.5 * sum(vecnorm(cross(p2-p1, p3-p1, 2), 2, 2));
  877. end
  878. function hVol = safeVolshow(viewerParent, V, varargin)
  879. % safeVolshow: wrapper around volshow that sets overlay colormap/alpha robustly
  880. % Usage:
  881. % hVol = safeVolshow(viewerParent, V, 'OverlayData', overlay, ...
  882. % 'OverlayColormap', cmap, 'OverlayAlpha', 0.2, ...)
  883. %
  884. % Pass the same name-value pairs you would normally pass to volshow.
  885. % If 'OverlayAlpha' is present in varargin, this function will try to
  886. % apply it using a supported property name for the user's MATLAB release.
  887. % parse inputs quickly
  888. p = inputParser;
  889. addRequired(p,'viewerParent');
  890. addRequired(p,'V');
  891. parse(p,viewerParent,V);
  892. % find OverlayAlpha in varargin (case-sensitive)
  893. overlayAlpha = [];
  894. idxAlpha = find(strcmp('OverlayAlpha', varargin), 1);
  895. if ~isempty(idxAlpha)
  896. overlayAlpha = varargin{idxAlpha+1};
  897. % remove it from the varargin list so volshow doesn't choke on ambiguous name
  898. varargin([idxAlpha, idxAlpha+1]) = [];
  899. end
  900. % call volshow with remaining args
  901. try
  902. hVol = volshow(V, 'Parent', viewerParent, varargin{:});
  903. catch ME
  904. % try without 'Parent' if some users' volshow API differs
  905. try
  906. hVol = volshow(V, varargin{:});
  907. catch
  908. rethrow(ME)
  909. end
  910. end
  911. % if no overlayAlpha requested, return
  912. if isempty(overlayAlpha)
  913. return
  914. end
  915. % Try to set overlay alpha using supported property names
  916. % List of possible property names seen across MATLAB versions:
  917. candidateProps = { ...
  918. 'OverlayAlpha', ... % exact name you used
  919. 'OverlayAlphaData', ... % some releases
  920. 'OverlayAlphaMap', ... % other variants
  921. 'OverlayOpacity', ... % hypothetical variant
  922. 'OverlayTransparency' ... % hypothetical variant
  923. };
  924. setSuccess = false;
  925. for i=1:numel(candidateProps)
  926. prop = candidateProps{i};
  927. try
  928. if isprop(hVol, prop)
  929. % If prop expects vector/array vs scalar: do an assignment attempt
  930. hVol.(prop) = overlayAlpha;
  931. setSuccess = true;
  932. break
  933. end
  934. catch
  935. % ignore and try next
  936. end
  937. end
  938. % Some releases implement overlay alpha on the *Volume* subclass but don't
  939. % expose isprop() in the usual way — try set() as last resort
  940. if ~setSuccess
  941. try
  942. set(hVol, 'OverlayAlpha', overlayAlpha);
  943. setSuccess = true;
  944. catch
  945. % ignore
  946. end
  947. end
  948. if ~setSuccess
  949. warning(['Could not set overlay alpha on this MATLAB release. ' ...
  950. 'Overlay will be shown with default opacity. If you need ' ...
  951. 'per-overlay transparency, check that your MATLAB release ' ...
  952. 'supports it or update MATLAB.']);
  953. end
  954. end

Meslier_3DDynamicHisto_QM_r1.m at commit cb0261d, under MIT · at the source

Overview

Authors: Quentin A. Meslier1,2, Nicole Migotsky3, Syeda N. Lamia3, Erica L. Scheller1,2,4, Matthew J. Silva3,4
  1. Bone and Mineral Disease Division, Department of Medicine, School of Medicine, Washington University in St. Louis, St. Louis, MO, USA
  2. Center of Regenerative Medicine, School of Medicine, Washington University in St. Louis, St. Louis, MO, USA
  3. Department of Orthopedic Surgery, School of Medicine, Washington University in St. Louis, St. Louis, MO, USA
  4. Department of Biomedical Engineering, McKelvey School of Engineering, Washington University in St. Louis, St. Louis, MO, USA
Institutions: Washington University in St. Louis (United States)
Journal: STAR protocols, volume 7, issue 3, article 104787
Dates: published online 25 August 2026; in print August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.xpro.2026.104787 · PMID 42647171 · PMCID PMC13543904 · OpenAlex W7204202655
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: mouse (organism)
Methods: Connectivity, fMRI & imaging
Keywords: Biophysics, Structural biology, Biotechnology and bioengineering
Topic: Advanced X-ray Imaging Techniques (Radiation, Physics and Astronomy), according to OpenAlex
Funding: NIH (R01-DK132073, R01 AR047867, R21 AR079052); Musculoskeletal Research Center; Washington University (P30-AR074992)
Citations: cited by 1 paper (Europe PMC); 12 references in the paper

Abstract

Serial in vivo microCT enables the quantification of dynamic bone remodeling by capturing formation and resorption over time. Here, we present a protocol to register pre- and post-intervention scans of rodent long bones through an intuitive drag-and-click workflow. We describe steps for rigid registration, enabling spatial mapping and quantification of formed, resorbed, and quiescent cortical and trabecular bone at periosteal and endosteal surfaces across multiple tibial regions. This protocol provides a non-destructive and complementary alternative to classic, fluorochrome-based dynamic histomorphometry.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.

QMuentin/3D-digital-dynamic-histomorphometry

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: cb0261dad0eb045552cd0918c0168ee9ba5d43a9, 25 March 2026
Languages: MATLAB (1), Python (1)
Size: 6 files, 2 scripts
Software Heritage: not archived
Found in: “Data and code availability”
Holds: README, license file, CITATION.cff
Not found: environment file, tests, continuous integration, documentation
Tools: Image Processing Toolbox (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
4 files

Zenodo 19225261

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Data and code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Image Processing Toolbox (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
4 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 4 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and code availability

• The original MATLAB code and Dragonfly macro generated during this study are available on GitHub at 3D-digital-dynamic-histomorphometry (https://github.com/QMuentin/3D-digital-dynamic-histomorphometry, https://doi.org/10.5281/ZENODO.19225261).

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 3 keywords, 3 funders, 12 references.

Cite

This paper

Meslier, Q. A., Migotsky, N., Lamia, S. N., Scheller, E. L., & Silva, M. J. (2026). Protocol for 3D digital dynamic histomorphometry of mouse bone via time-lapse registration of serial microCT scans. STAR protocols, 7(3), 104787. https://doi.org/10.1016/j.xpro.2026.104787

BibTeX

@article{meslier2026protocol,
author = {Meslier, Quentin A. and Migotsky, Nicole and Lamia, Syeda N. and Scheller, Erica L. and Silva, Matthew J.},
title = {{Protocol for 3D digital dynamic histomorphometry of mouse bone via time-lapse registration of serial microCT scans}},
journal = {STAR protocols},
year = {2026},
month = aug,
volume = {7},
number = {3},
pages = {104787},
publisher = {Elsevier},
issn = {2666-1667},
doi = {10.1016/j.xpro.2026.104787},
url = {https://doi.org/10.1016/j.xpro.2026.104787},
pmid = {42647171},
pmcid = {PMC13543904}
}

RIS

TY - JOUR
AU - Meslier, Quentin A.
AU - Migotsky, Nicole
AU - Lamia, Syeda N.
AU - Scheller, Erica L.
AU - Silva, Matthew J.
TI - Protocol for 3D digital dynamic histomorphometry of mouse bone via time-lapse registration of serial microCT scans
T2 - STAR protocols
J2 - STAR Protoc
PY - 2026
DA - 2026/08/25
VL - 7
IS - 3
SP - 104787
SN - 2666-1667
PB - Elsevier
DO - 10.1016/j.xpro.2026.104787
UR - https://doi.org/10.1016/j.xpro.2026.104787
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.xpro.2026.104787",
"type": "article-journal",
"title": "Protocol for 3D digital dynamic histomorphometry of mouse bone via time-lapse registration of serial microCT scans",
"container-title": "STAR protocols",
"author": [
{
"family": "Meslier",
"given": "Quentin A."
},
{
"family": "Migotsky",
"given": "Nicole"
},
{
"family": "Lamia",
"given": "Syeda N."
},
{
"family": "Scheller",
"given": "Erica L."
},
{
"family": "Silva",
"given": "Matthew J."
}
],
"container-title-short": "STAR Protoc",
"volume": "7",
"issue": "3",
"page": "104787",
"DOI": "10.1016/j.xpro.2026.104787",
"PMID": "42647171",
"PMCID": "PMC13543904",
"ISSN": "2666-1667",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.xpro.2026.104787",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
25
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41467-026-72845-3 [code]
The membrane-to-cortex distance regulates mDia1 activity to control cortical mechanics.
Journal: Nature communications
In common: Image Processing Toolbox, mouse, 1 reference
[2] doi:10.1016/j.isci.2026.117187 [code]
Functional and structural characterization of dendritic spine pathology in a mouse model of tauopathy.
Journal: iScience
In common: Image Processing Toolbox, mouse, 1 reference
[3] doi:10.1038/s41467-026-73476-4 [code]
Developmental molecular signatures define de novo cortico-brainstem circuit for skilled forelimb movement.
Journal: Nature communications
In common: Image Processing Toolbox, mouse, 1 reference
[4] doi:10.1038/s41592-026-03066-1 [code]
A multimodal adaptive optical microscope for in vivo imaging from molecules to organisms.
Journal: Nature methods
In common: Image Processing Toolbox, mouse, 1 reference
[5] doi:10.1002/advs.202524341 [code]
Temporal Interference Stimulation Enhances Neural Regeneration.
Journal: Advanced science (Weinheim, Baden-Wurttemberg, Germany)
In common: Image Processing Toolbox, mouse, 1 reference
[6] doi:10.1530/joe-25-0462
Membrane-initiated estrogen receptor-α signaling in the hypothalamus regulates trabecular bone in femur in female mice.
Journal: The Journal of endocrinology
In common: mouse, 1 reference
[7] doi:10.1111/jnc.70551 [code]
Synaptobrevin-2 Containing Extracellular Vesicles Are Rapidly Incorporated Into Mammalian Neurons via a Dynamin-Dependent Pathway.
Journal: Journal of neurochemistry
In common: Image Processing Toolbox, 1 reference
[8] doi:10.1016/j.isci.2026.117375 [code]
Motor priming is associated with widespread recruitment into neural ensembles and more rapid ensemble transitions.
Journal: iScience
In common: Image Processing Toolbox, 1 reference
[9] doi:10.1016/j.isci.2026.117212 [code]
Critical neuronal avalanches arise from excitation-inhibition balanced spontaneous activity.
Journal: iScience
In common: Image Processing Toolbox, 1 reference
[10] doi:10.1016/j.cub.2026.06.016 [code]
Neuronal RNAi and oxygen-sensing circuit shape germline resilience to heat stress.
Journal: Current biology : CB
In common: Image Processing Toolbox, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.