OSCR

DRIFT-EM enables direct wafer retrieval of ultrathin serial sections for large-volume electron microscopy.

Code ↔ Paper

3 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 3 matches
  1. [1] § STAR★Methods › Method details › Computational pipeline › Automatic ROI detection ↔ detect_rectangles.py, lines 15–67 · score 0.84 · capture subtle edges, Sobel operators, low threshold, gradients, kernels, magnitude
  2. [2] § STAR★Methods › Method details › Computational pipeline › Automatic ROI detection ↔ detect_rectangles.py, lines 15–67 · score 0.72 · external contour, dilated edges, kernel, subtracted, binary, detection
  3. [3] § STAR★Methods › Method details › Computational pipeline › Nearest-neighbor ordering ↔ generate_and_fuse.py, lines 274–324 · score 0.50 · nearest neighbor algorithm, travel, distance

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

Python · 663 lines · 28 KB · no license · 2 matches

  1. #!/usr/bin/env python3
  2. import cv2
  3. import numpy as np
  4. import argparse
  5. import random # For selecting random contours
  6. from scipy.optimize import minimize # For advanced optimization
  7. import datetime # For timestamped output filenames
  8. import os # For path operations
  9. import pandas as pd # For creating Excel files
  10. # Set fixed random seed for reproducibility
  11. random.seed(42) # Always use the same random seed for consistent results
  12. np.random.seed(42)
  13. def process_image(image_path):
  14. """
  15. Process an image to detect contours with edge subtraction method using Sobel.
  16. Args:
  17. image_path (str): Path to the input image
  18. Returns:
  19. tuple: (image, binary, contours, edges, binary_minus_edges)
  20. """
  21. # Read the image
  22. image = cv2.imread(image_path)
  23. # Convert to grayscale if it's not already
  24. if len(image.shape) == 3:
  25. gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
  26. else:
  27. gray = image.copy()
  28. # Fixed threshold at value 86 (for improved rectangle detection)
  29. _, binary = cv2.threshold(gray, 86, 255, cv2.THRESH_BINARY_INV)
  30. # Apply contrast enhancement to emphasize gradients
  31. clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
  32. gray_enhanced = clahe.apply(gray)
  33. # Apply Sobel operators with large kernel size
  34. sobelx = cv2.Sobel(gray_enhanced, cv2.CV_64F, 1, 0, ksize=5)
  35. sobely = cv2.Sobel(gray_enhanced, cv2.CV_64F, 0, 1, ksize=5)
  36. # Calculate magnitude
  37. magnitude = np.sqrt(sobelx**2 + sobely**2)
  38. # Normalize to 0-255 range
  39. magnitude = cv2.normalize(magnitude, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
  40. # Apply a low threshold to capture subtle edges
  41. _, edges = cv2.threshold(magnitude, 20, 255, cv2.THRESH_BINARY)
  42. # Apply three iterations of dilation with a 4x4 kernel
  43. kernel = np.ones((4, 4), np.uint8)
  44. dilated_edges = edges.copy()
  45. for _ in range(3):
  46. dilated_edges = cv2.dilate(dilated_edges, kernel, iterations=1)
  47. # Use the dilated edges for subtraction
  48. binary_minus_edges = cv2.bitwise_and(binary, binary, mask=cv2.bitwise_not(dilated_edges))
  49. # Find only external contours using the binary_minus_edges image
  50. contours, _ = cv2.findContours(binary_minus_edges.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  51. # Return the dilated edges instead of the original edges
  52. return image, binary_minus_edges, contours, dilated_edges, binary_minus_edges
  53. def optimize_rectangle_for_contour(image, contour, target_width=36, target_height=24, occupied_mask=None):
  54. # Store the grayscale image for optimization
  55. # We will optimize to find the darkest region (lowest pixel values)
  56. gray_for_opt = image.copy()
  57. # Create a binary mask from the contour
  58. x, y, w, h = cv2.boundingRect(contour)
  59. mask = np.zeros((h, w), dtype=np.uint8)
  60. # Shift contour to the local coordinates of the bounding rectangle
  61. shifted_contour = contour - np.array([x, y])
  62. # Draw the contour on the mask
  63. cv2.drawContours(mask, [shifted_contour], 0, 255, -1) # Fill the contour
  64. # Calculate moments from the binary mask
  65. M = cv2.moments(mask)
  66. if M["m00"] != 0:
  67. # Get center of mass in local coordinates
  68. local_cx = int(M["m10"] / M["m00"])
  69. local_cy = int(M["m01"] / M["m00"])
  70. # Convert to global coordinates
  71. cx = x + local_cx
  72. cy = y + local_cy
  73. # Calculate initial angle from moments (more accurate orientation)
  74. # Using central moments to get principal axes orientation
  75. mu20 = M["mu20"] / M["m00"]
  76. mu02 = M["mu02"] / M["m00"]
  77. mu11 = M["mu11"] / M["m00"]
  78. # Calculate the orientation angle in degrees
  79. if mu20 != mu02:
  80. moments_angle = 0.5 * np.degrees(np.arctan2(2 * mu11, mu20 - mu02))
  81. # Normalize to 0-180 range
  82. moments_angle = moments_angle % 180
  83. else:
  84. moments_angle = 0 # Default to horizontal
  85. else:
  86. # Fallback to bounding rect center (should rarely happen)
  87. cx, cy = x + w//2, y + h//2
  88. moments_angle = 0 # Default to horizontal
  89. # Choose initial angle (prefer moments if available)
  90. initial_angle = moments_angle
  91. # Normalize angle to 0-180 range for consistency
  92. initial_angle = initial_angle % 180
  93. # Cache for optimization - prevents recreating the image on every iteration
  94. h, w = gray_for_opt.shape[:2]
  95. def rect_cost_components(params):
  96. """
  97. Calculate the individual cost components for a rectangle.
  98. Args:
  99. params: [center_x, center_y, angle_radians]
  100. Returns:
  101. tuple: (brightness_cost, angle_penalty_cost, overlap_penalty)
  102. """
  103. cx, cy, angle_rad = params
  104. angle_deg = np.degrees(angle_rad)
  105. # Constrain to image boundaries
  106. cx = np.clip(cx, 0, w-1)
  107. cy = np.clip(cy, 0, h-1)
  108. # Create a rotated rectangle
  109. rect = ((cx, cy), (target_width, target_height), angle_deg)
  110. box = cv2.boxPoints(rect)
  111. box = box.astype(np.int32)
  112. # Check for overlap with existing rectangles using the occupied mask
  113. overlap_penalty = 0.0
  114. if occupied_mask is not None:
  115. # Create a mask for this candidate rectangle
  116. current_rect_mask = np.zeros((h, w), dtype=np.uint8)
  117. cv2.fillPoly(current_rect_mask, [box], 1)
  118. # Check if this rectangle overlaps with any existing rectangles
  119. # by comparing with the occupied mask
  120. overlap = np.logical_and(current_rect_mask, occupied_mask).sum()
  121. # If there's any overlap, apply a very large penalty
  122. if overlap > 0:
  123. overlap_penalty = np.sum(overlap)
  124. # Create a mask for this rectangle for brightness calculation
  125. mask = np.zeros((h, w), dtype=np.uint8)
  126. cv2.fillPoly(mask, [box], 255)
  127. # Extract region using the mask and calculate the sum of pixel values
  128. # This will measure the total "brightness" inside the rectangle
  129. # For a grayscale image, lower values mean darker pixels
  130. masked_region = cv2.bitwise_and(gray_for_opt, gray_for_opt, mask=mask)
  131. # Count non-zero pixels (where mask is applied)
  132. non_zero_count = cv2.countNonZero(mask)
  133. # Default to high cost if no pixels in mask
  134. if non_zero_count == 0:
  135. return float('inf'), 0.0, float('inf')
  136. brightness = np.sum(masked_region) / non_zero_count
  137. current_angle = angle_deg % 180
  138. contour_angle = initial_angle % 180
  139. # Calculate angle deviation cost (penalty for rotating too far from initial angle)
  140. # Handle the discontinuity at 0/180 degrees
  141. angle_diff = min(
  142. abs(current_angle - contour_angle), # Regular difference
  143. abs(current_angle - (contour_angle + 180) % 180), # Wrap around one way
  144. abs((current_angle + 180) % 180 - contour_angle) # Wrap around other way
  145. )
  146. angle_penalty = angle_diff * 0.75
  147. return float(brightness), float(angle_penalty), float(overlap_penalty)
  148. def rect_overlap_cost(params):
  149. """
  150. Cost function for optimization: sum of pixel values inside the rectangle
  151. (to be minimized). Lower pixel values (darker) are better.
  152. Args:
  153. params: [center_x, center_y, angle_radians]
  154. Returns:
  155. float: Sum of pixel values (lower is better, indicating darker area)
  156. """
  157. brightness, angle_penalty, overlap_penalty = rect_cost_components(params)
  158. # Combine the costs - brightness is our main term, angle penalty is secondary
  159. combined_cost = brightness + angle_penalty + overlap_penalty
  160. return combined_cost
  161. # Initial parameters [x, y, angle_radians]
  162. initial_params = [cx, cy, np.radians(initial_angle)]
  163. # Bounds for the parameters:
  164. # x: can move ±5 pixels from center
  165. # y: can move ±5 pixels from center
  166. # angle: can rotate ±90 degrees from initial angle
  167. bounds = [
  168. (max(0, cx - 5), min(w-1, cx + 5)),
  169. (max(0, cy - 5), min(h-1, cy + 5)),
  170. (np.radians(initial_angle - 90), np.radians(initial_angle + 90))
  171. ]
  172. print(bounds)
  173. # Use the minimize function for optimization
  174. result = minimize(
  175. rect_overlap_cost,
  176. initial_params,
  177. method='L-BFGS-B', # Works well with bounds
  178. bounds=bounds,
  179. options={'maxiter': 100}
  180. )
  181. # Get the optimized parameters
  182. opt_cx, opt_cy, opt_angle_rad = result.x
  183. opt_angle_deg = np.degrees(opt_angle_rad) % 180
  184. # Calculate the final cost for reporting
  185. final_cost = rect_overlap_cost([opt_cx, opt_cy, opt_angle_rad])
  186. return {
  187. 'center_x': int(opt_cx),
  188. 'center_y': int(opt_cy),
  189. 'angle': opt_angle_deg,
  190. 'width': target_width,
  191. 'height': target_height,
  192. 'score': final_cost,
  193. 'success': result.success
  194. }
  195. def save_debug_image(image_path, min_area=0, max_area=float('inf'), num_rects=5, save_excel=True):
  196. """
  197. Save high-resolution debug images showing:
  198. A) Original image with optimized 24x36 rectangles
  199. B) Detected contours with random colors (contours outside size limits are shown in gray)
  200. C) Edge detection image (Sobel)
  201. D) Binary minus edges (the processed image used for detection)
  202. Args:
  203. image_path (str): Path to the input image
  204. min_area (int): Minimum contour area to highlight in color
  205. max_area (int): Maximum contour area to highlight in color
  206. num_rects (int): Number of random contours to optimize and display
  207. save_excel (bool): Whether to save contour data to Excel file
  208. Returns:
  209. str: Path to the saved debug image
  210. """
  211. # Generate timestamp for unique filename
  212. timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
  213. # Get input file name without extension
  214. base_name = os.path.splitext(os.path.basename(image_path))[0]
  215. # Create output path with timestamp in current directory
  216. output_path = f"debug_{base_name}_{timestamp}.tiff"
  217. print(f"Processing debug image for {image_path}...")
  218. print(f"Output will be saved to {output_path}")
  219. # Process image with edge subtraction and Sobel method
  220. print(f"Steps 1-5: Processing image using edge subtraction with Sobel...")
  221. # Use edge subtraction method
  222. image, binary, contours, edges, binary_minus_edges = process_image(image_path)
  223. # Save contour data to Excel if requested
  224. if save_excel:
  225. print("Saving contour data to Excel file...")
  226. contour_data = []
  227. for i, cnt in enumerate(contours):
  228. # Calculate contour area
  229. area = cv2.contourArea(cnt)
  230. # Calculate center of mass using moments
  231. M = cv2.moments(cnt)
  232. if M["m00"] != 0:
  233. cx = int(M["m10"] / M["m00"])
  234. cy = int(M["m01"] / M["m00"])
  235. else:
  236. # Fallback to bounding rect center if moments calculation fails
  237. x, y, w, h = cv2.boundingRect(cnt)
  238. cx, cy = x + w//2, y + h//2
  239. # Calculate bounding rectangle dimensions
  240. x, y, w, h = cv2.boundingRect(cnt)
  241. # Calculate rectangle fit quality
  242. # Get the minimum area rectangle that fits the contour
  243. rect = cv2.minAreaRect(cnt)
  244. rect_width, rect_height = rect[1]
  245. # Make sure width is the smaller dimension and height is the larger one
  246. if rect_width > rect_height:
  247. rect_width, rect_height = rect_height, rect_width
  248. # Calculate area of the min area rectangle
  249. rect_area = rect_width * rect_height
  250. # Calculate fill ratio (how well the contour fills the rectangle)
  251. # Higher ratio means better rectangle fit
  252. fill_ratio = area / rect_area if rect_area > 0 else 0
  253. # Calculate aspect ratio of the rectangle (height/width)
  254. aspect_ratio = rect_height / rect_width if rect_width > 0 else 0
  255. # Calculate how well the contour fits a 9x20 rectangle
  256. # Target aspect ratio is 20/9 = 2.22
  257. target_aspect_ratio = 20.0 / 9.0
  258. # Calculate aspect ratio fit score (lower is better)
  259. aspect_ratio_fit = abs(aspect_ratio - target_aspect_ratio)
  260. # Calculate size fit (how close is the contour to the target size)
  261. target_area = 9 * 20
  262. # Avoid division by zero
  263. if area == 0:
  264. area_ratio = 0 # If area is zero, it's a poor fit
  265. else:
  266. area_ratio = min(area / target_area, target_area / area) # Between 0 and 1, higher is better
  267. # Combined 9x20 fit score (higher is better)
  268. # Weigh aspect ratio fit more heavily
  269. rect_9x20_fit = (0.7 * (1 - aspect_ratio_fit / 3.0)) + (0.3 * area_ratio)
  270. # Clamp the score between 0 and 1
  271. rect_9x20_fit = max(0, min(1, rect_9x20_fit))
  272. # Add data to the list
  273. contour_data.append({
  274. 'Contour_ID': i,
  275. 'Area': area,
  276. 'Center_X': cx,
  277. 'Center_Y': cy,
  278. 'Bounding_Box_X': x,
  279. 'Bounding_Box_Y': y,
  280. 'Bounding_Box_Width': w,
  281. 'Bounding_Box_Height': h,
  282. 'Min_Rect_Width': round(rect_width, 2),
  283. 'Min_Rect_Height': round(rect_height, 2),
  284. 'Min_Rect_Area': round(rect_area, 2),
  285. 'Rectangle_Fill_Ratio': round(fill_ratio, 4),
  286. 'Aspect_Ratio': round(aspect_ratio, 4),
  287. 'Rect_9x20_Fit_Score': round(rect_9x20_fit, 4)
  288. })
  289. # Create DataFrame and save to Excel
  290. df = pd.DataFrame(contour_data)
  291. # Sort by 9x20 rectangle fit score (highest first)
  292. df = df.sort_values(by='Rect_9x20_Fit_Score', ascending=False)
  293. df.to_excel('d.xlsx', index=False)
  294. print(f"Saved data for {len(contours)} contours to d.xlsx (sorted by 9x20 rectangle fit)")
  295. # Create a copy of the original image for panel A
  296. panel_a_image = image.copy()
  297. # Create a 2x2 grid
  298. print("Step 6: Creating composite image grid...")
  299. # Get dimensions from the input image
  300. img_height, img_width = image.shape[:2]
  301. # Set fixed output dimensions for debug view
  302. height, width = binary.shape # Standard debug size for all panels
  303. scale_factor = 1 # Scale factor for output resolution
  304. combined_height = height * 2 * scale_factor
  305. combined_width = width * 2 * scale_factor
  306. combined = np.zeros((combined_height, combined_width, 3), dtype=np.uint8)
  307. # Convert grayscale images to BGR for visualization
  308. print("Step 7: Converting images for visualization...")
  309. edges_bgr = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
  310. binary_minus_edges_bgr = cv2.cvtColor(binary_minus_edges, cv2.COLOR_GRAY2BGR)
  311. # Ensure panel_a_image is in BGR format if it's grayscale
  312. if len(panel_a_image.shape) == 2 or panel_a_image.shape[2] == 1:
  313. panel_a_image = cv2.cvtColor(panel_a_image, cv2.COLOR_GRAY2BGR)
  314. # No need to resize if scale_factor is 1
  315. print("Step 8: Preparing images for visualization...")
  316. # Create contour visualization image
  317. print("Step 9a: Creating contour visualization image with size filtering...")
  318. contour_image = image.copy()
  319. # Count valid contours (within size limits)
  320. valid_contours = 0
  321. valid_contour_list = []
  322. # Generate a random color for each contour
  323. for i, contour in enumerate(contours):
  324. # Get contour area
  325. area = cv2.contourArea(contour)
  326. # Check if contour is within size limits
  327. if min_area <= area <= max_area:
  328. # Valid contour - use a random bright color
  329. color = np.random.randint(0, 255, size=3).tolist()
  330. # Ensure the color has at least one bright channel for visibility
  331. max_channel = max(color)
  332. if max_channel < 150: # If all channels are dim
  333. bright_channel = np.random.randint(0, 3) # Choose a random channel to make bright
  334. color[bright_channel] = 255 # Make that channel bright
  335. valid_contours += 1
  336. valid_contour_list.append((contour, color))
  337. else:
  338. # Invalid contour - use gray
  339. color = [120, 120, 120] # Gray color for contours outside size limits
  340. # Draw contour outline slightly thicker for better visibility
  341. cv2.drawContours(contour_image, [contour], -1, color, 2)
  342. # Optimize and draw rectangles if we have valid contours
  343. if valid_contour_list:
  344. # Create a list of contours to optimize, picking contours with highest 9x20 fit score
  345. valid_contours_with_score = []
  346. for contour, color in valid_contour_list:
  347. # Calculate how well this contour fits a 9x20 rectangle
  348. # Get the minimum area rectangle
  349. rect = cv2.minAreaRect(contour)
  350. rect_width, rect_height = rect[1]
  351. # Make sure width is the smaller dimension
  352. if rect_width > rect_height:
  353. rect_width, rect_height = rect_height, rect_width
  354. # Calculate aspect ratio
  355. aspect_ratio = rect_height / rect_width if rect_width > 0 else 0
  356. # Target aspect ratio for 9x20
  357. target_aspect_ratio = 20.0 / 9.0
  358. # Aspect ratio fit score (lower is better)
  359. aspect_ratio_fit = abs(aspect_ratio - target_aspect_ratio)
  360. # Calculate area
  361. area = cv2.contourArea(contour)
  362. target_area = 9 * 20
  363. # Avoid division by zero
  364. if area == 0:
  365. area_ratio = 0 # If area is zero, it's a poor fit
  366. else:
  367. area_ratio = min(area / target_area, target_area / area) # Between 0 and 1, higher is better
  368. # Combined fit score (higher is better)
  369. fit_score = (0.7 * (1 - aspect_ratio_fit / 3.0)) + (0.3 * area_ratio)
  370. fit_score = max(0, min(1, fit_score))
  371. valid_contours_with_score.append((contour, color, fit_score))
  372. # Sort by fit score (highest first)
  373. valid_contours_with_score.sort(key=lambda x: x[2], reverse=True)
  374. # Select top contours (up to num_rects)
  375. num_to_optimize = min(num_rects, len(valid_contours_with_score))
  376. contours_to_optimize = [(c, color) for c, color, _ in valid_contours_with_score[:num_to_optimize]]
  377. # Log that optimization is being performed
  378. print(f"Optimizing {num_to_optimize} rectangles (sorted by 9x20 fit)...")
  379. # Create a transparent overlay for the optimized rectangles
  380. overlay = panel_a_image.copy()
  381. # Draw the original contours in blue
  382. for contour, _ in contours_to_optimize:
  383. cv2.drawContours(overlay, [contour], 0, (255, 0, 0), 1) # Thin blue line
  384. # Create a mask to keep track of occupied areas
  385. h, w = image.shape[:2]
  386. occupied_mask = np.zeros((h, w), dtype=np.uint8)
  387. # Keep track of optimized rectangles (for visualization)
  388. optimized_rectangles = []
  389. # Now optimize each contour and draw the rectangles
  390. for i, (contour, _) in enumerate(contours_to_optimize):
  391. # Show progress in console
  392. print(f" Optimizing rectangle {i+1}/{num_to_optimize}...")
  393. # Optimize the rectangle placement, passing the occupied mask to avoid overlap
  394. opt_rect = optimize_rectangle_for_contour(image, contour, occupied_mask=occupied_mask)
  395. # Only use rectangles where optimization succeeded (not infinite cost)
  396. if opt_rect['score'] < float('inf'):
  397. # Add this rectangle to our list of optimized rectangles
  398. optimized_rectangles.append(opt_rect)
  399. # Update the occupied mask with this rectangle
  400. rect_center = (opt_rect['center_x'], opt_rect['center_y'])
  401. rect_size = (opt_rect['width'], opt_rect['height'])
  402. rect_angle = opt_rect['angle']
  403. # Create box points for the rectangle
  404. rect_box = cv2.boxPoints((rect_center, rect_size, rect_angle))
  405. rect_box = rect_box.astype(np.int32)
  406. # Add this rectangle to the occupied mask
  407. cv2.fillPoly(occupied_mask, [rect_box], 1)
  408. # Draw the optimized rectangle
  409. center = (opt_rect['center_x'], opt_rect['center_y'])
  410. size = (opt_rect['width'], opt_rect['height'])
  411. angle = opt_rect['angle']
  412. # Create the rotated rectangle points
  413. rect = (center, size, angle)
  414. box = cv2.boxPoints(rect)
  415. box = box.astype(np.int32)
  416. # Draw with a thin green line
  417. cv2.drawContours(overlay, [box], 0, (0, 255, 0), 1) # Thin green line
  418. # Log the optimization result
  419. print(f" Rectangle at ({center[0]}, {center[1]}), angle: {angle:.1f}°, score: {opt_rect['score']:.0f}")
  420. else:
  421. print("failure")
  422. # Apply the overlay with transparency
  423. alpha = 0.6 # 60% opacity (more visible than before)
  424. cv2.addWeighted(overlay, alpha, panel_a_image, 1 - alpha, 0, panel_a_image)
  425. print(f" Drew {num_to_optimize} optimized rectangles with semi-transparency")
  426. # Save optimized rectangles to a .magc file with values multiplied by 10
  427. if optimized_rectangles:
  428. print("Saving optimized rectangles to .magc file...")
  429. # Create a .magc file using the base name of the input image
  430. magc_output_path = f"{base_name}_{timestamp}.magc"
  431. with open(magc_output_path, 'w') as f:
  432. # Write the header with the number of sections
  433. f.write("[sections]\n")
  434. f.write(f"number = {len(optimized_rectangles)}\n\n")
  435. # Write each section
  436. for i, rect in enumerate(optimized_rectangles):
  437. # Extract rectangle information
  438. center_x = rect['center_x'] * 10 # Multiply by 10 as requested
  439. center_y = rect['center_y'] * 10 # Multiply by 10 as requested
  440. width_here = rect['width'] * 10 # Multiply by 10 as requested
  441. height_here = rect['height'] * 10 # Multiply by 10 as requested
  442. angle = rect['angle']
  443. # Calculate the corner points of the rectangle
  444. rect_points = cv2.boxPoints(((rect['center_x'], rect['center_y']),
  445. (rect['width'], rect['height']),
  446. rect['angle']))
  447. rect_points = rect_points.astype(np.float32)
  448. # Multiply the points by 10 for the .magc file
  449. rect_points *= 10
  450. # Format the polygon string
  451. polygon_str = ",".join([f"{p[0]:.1f},{p[1]:.1f}" for p in rect_points])
  452. # Calculate area (width * height * 10^2 since both dimensions are multiplied by 10)
  453. area = width_here * height_here
  454. # Write the section
  455. f.write(f"[section.{i:04d}]\n")
  456. f.write(f"polygon = {polygon_str}\n")
  457. f.write(f"center = {center_x:.2f},{center_y:.2f}\n")
  458. f.write(f"area = {area:.1f}\n")
  459. f.write(f"angle = {angle}\n\n")
  460. print(f"Saved {len(optimized_rectangles)} rectangles to {magc_output_path} with values multiplied by 10")
  461. # Ensure contour_image is in BGR format if it's grayscale
  462. if len(contour_image.shape) == 2 or contour_image.shape[2] == 1:
  463. contour_image = cv2.cvtColor(contour_image, cv2.COLOR_GRAY2BGR)
  464. # Place each image in the grid
  465. print("Step 9b: Arranging images in grid...")
  466. combined[0:height, 0:width] = panel_a_image # A) Original with optimized rectangles
  467. combined[0:height, width:width*2] = contour_image # B) Detected Contours
  468. combined[height:height*2, 0:width] = edges_bgr # C) Edge detection
  469. combined[height:height*2, width:width*2] = binary_minus_edges_bgr # D) Binary minus Edges
  470. # Add text labels
  471. print("Step 10: Adding text labels...")
  472. font = cv2.FONT_HERSHEY_SIMPLEX
  473. font_scale = 0.5 # Smaller font scale for fixed-size debug view
  474. font_thickness = 1
  475. cv2.putText(combined, "A) Optimized 24x36 Rectangles", (10, 20), font, font_scale, (0, 255, 255), font_thickness)
  476. cv2.putText(combined, "B) Detected Contours", (width + 10, 20), font, font_scale, (0, 255, 255), font_thickness)
  477. cv2.putText(combined, "C) Edge Detection (Sobel)", (10, height + 20), font, font_scale, (0, 255, 255), font_thickness)
  478. cv2.putText(combined, "D) Binary minus Edges", (width + 10, height + 20), font, font_scale, (0, 255, 255), font_thickness)
  479. # Add contour count as text overlay
  480. total_contours = len(contours)
  481. cv2.putText(combined, f"Total Contours: {total_contours} (Valid: {valid_contours})",
  482. (10, combined_height - 10), cv2.FONT_HERSHEY_SIMPLEX,
  483. 0.5, (255, 255, 255), 1)
  484. # Add size thresholds and method info
  485. cv2.putText(combined, f"Size range: {min_area}-{max_area}",
  486. (10, combined_height - 30), cv2.FONT_HERSHEY_SIMPLEX,
  487. 0.5, (255, 255, 255), 1)
  488. # Add timestamp to the image
  489. cv2.putText(combined, f"Time: {timestamp}",
  490. (combined_width // 2, combined_height - 10), cv2.FONT_HERSHEY_SIMPLEX,
  491. 0.5, (255, 255, 255), 1)
  492. cv2.putText(combined, "Method: Edge Subtraction (Sobel)",
  493. (combined_width // 2, combined_height - 30), cv2.FONT_HERSHEY_SIMPLEX,
  494. 0.5, (255, 255, 255), 1)
  495. # Save the debug image as TIFF
  496. print("Step 12: Writing output TIFF image...")
  497. cv2.imwrite(output_path, combined) # TIFF format preserves full quality
  498. print(f"Debug image saved to {output_path}")
  499. print("Debug image processing completed successfully!")
  500. return output_path
  501. if __name__ == "__main__":
  502. parser = argparse.ArgumentParser(description='Generate debug image visualization')
  503. parser.add_argument('--image_path', default="5x_gregc_ORG_10p.tif", help='Path to the input image')
  504. parser.add_argument('--min-area', type=int, default=1, help='Minimum contour area')
  505. parser.add_argument('--max-area', type=int, default=400, help='Maximum contour area')
  506. parser.add_argument('--num-rects', type=int, default=5000,
  507. help='Number of random contours to optimize (default: 5)')
  508. parser.add_argument('--random-seed', type=int, default=42,
  509. help='Random seed for consistent contour selection (default: 42)')
  510. parser.add_argument('--excel', action='store_true', default=True,
  511. help='Save contour data to Excel file (default: True)')
  512. args = parser.parse_args()
  513. random.seed(args.random_seed)
  514. # Save debug image with automatic timestamped filename
  515. output_path = save_debug_image(
  516. args.image_path,
  517. args.min_area,
  518. args.max_area,
  519. args.num_rects,
  520. args.excel)

detect_rectangles.py at commit 69ba659, no license · at the source

Overview

Authors: Nelson Medina1,2,3, Joseph V. Gogola2,3,4, Kevin Boergens2,5, Fuming Yang6, Yaron Meirovitch6, Jeff W. Lichtman6, Narayanan Kasthuri2,3, Gregg Wildenberg2,3
ORCID iDs: Gregg Wildenberg
  1. MRC Laboratory of Molecular Biology, Cambridge Biomedical Campus, Cambridge CB2 0QH, MA, UK
  2. Department of Neurobiology, The University of Chicago, Chicago IL 60637, USA
  3. Argonne National Laboratory, Biosciences Division, Lemont IL 60439, USA
  4. Department of Medicine, The University of Chicago, Chicago IL 60637, USA
  5. Department of Physics, University of Illinois Chicago, Chicago IL 60607, USA
  6. Department of Molecular and Cellular Biology, Harvard University, Cambridge MA 02138, USA
Institutions: Argonne National Laboratory (United States); MRC Laboratory of Molecular Biology (United Kingdom); University of Chicago (United States); University of Illinois Chicago (United States); Harvard University (United States)
Journal: Cell reports methods, volume 6, issue 6, article 101429
Dates: received 5 December 2025; accepted 6 April 2026; published online 5 May 2026; in print June 2026
Type: Brief report · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1016/j.crmeth.2026.101429 · PMID 42086049 · PMCID PMC13282653 · OpenAlex W7160299588
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: histology / microscopy (modality), methods / tools (subfield)
Methods: fMRI & imaging
Keywords: connectomics, large-volume serial electron microscopy, vEM, ultramicrotomy, brain mapping
MeSH: Microtomy*, Volume Electron Microscopy*, Animals, Software (* major topic)
Topic: Advanced Electron Microscopy Techniques and Applications (Structural Biology, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: NIH (1U01NS136401–01, U01NS132158); UMass (R01 R01NS133654); UM1 (UM1NS132250); NSF Neuro Nex (#2014862)
Citations: not cited yet (Europe PMC); 29 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

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

Zenodo 19380076

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Materials availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
1 file

figshare 31836088

License: CC-BY-4.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Size: 2 files
Software Heritage: not checked
Found in: “Data and code availability”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)

Zenodo 19372693

License: MIT
State: the link answers, verified on 28 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: pandas (3 files), Matplotlib (1 file), NetworkX (1 file), NumPy (1 file), OpenCV (1 file), scikit-image (1 file), SciPy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
5 files

Zenodo 19380050

License: CC-BY-4.0
State: the link answers, verified on 28 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: NumPy (3 files), OpenCV (2 files), Pillow (2 files), NetworkX (1 file), pandas (1 file), SciPy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers (HTTP 200)
  • 28 September 2026: the link answers (HTTP 200)
3 files

boergens/DRIFT-EM

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 69ba659c31935c04e3b1dd5ba318204e9df42122, 3 November 2025
Languages: Python (3)
Size: 8 files, 3 scripts
Software Heritage: not archived
Found in: the text, “Automatic ROI detection”
Holds: environment (requirements.txt)
Not found: README, license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (3 files), OpenCV (2 files), Pillow (2 files), NetworkX (1 file), pandas (1 file), SciPy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
3 files

templiert/MagC

License: GPL-3.0
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: e1a89cf12cbfe61f1b2a4e844483dbf8a2b1a749, 21 October 2019
Languages: Python (28), Shell (1)
Size: 35 files, 29 scripts
Software Heritage: not archived
Found in: the text, “Automatic ROI detection”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: ImageJ / Fiji (21 files), NumPy (4 files), Matplotlib (2 files), Pillow (2 files), scikit-image (2 files), SciPy (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
31 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:

  • 6 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 38 scripts, each with its path and the digest of its content;
  • 3 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

Datasets cited

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1016/j.crmeth.2026.101429.

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, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 8 authors, 5 keywords, 4 MeSH terms, 4 funders, 28 references.

Cite

This paper

Medina, N., Gogola, J. V., Boergens, K., Yang, F., Meirovitch, Y., Lichtman, J. W., Kasthuri, N., & Wildenberg, G. (2026). DRIFT-EM enables direct wafer retrieval of ultrathin serial sections for large-volume electron microscopy. Cell reports methods, 6(6), 101429. https://doi.org/10.1016/j.crmeth.2026.101429

BibTeX

@article{medina2026drift,
author = {Medina, Nelson and Gogola, Joseph V. and Boergens, Kevin and Yang, Fuming and Meirovitch, Yaron and Lichtman, Jeff W. and Kasthuri, Narayanan and Wildenberg, Gregg},
title = {{DRIFT-EM enables direct wafer retrieval of ultrathin serial sections for large-volume electron microscopy}},
journal = {Cell reports methods},
year = {2026},
month = may,
volume = {6},
number = {6},
pages = {101429},
publisher = {Elsevier},
issn = {2667-2375},
doi = {10.1016/j.crmeth.2026.101429},
url = {https://doi.org/10.1016/j.crmeth.2026.101429},
pmid = {42086049},
pmcid = {PMC13282653}
}

RIS

TY - JOUR
AU - Medina, Nelson
AU - Gogola, Joseph V.
AU - Boergens, Kevin
AU - Yang, Fuming
AU - Meirovitch, Yaron
AU - Lichtman, Jeff W.
AU - Kasthuri, Narayanan
AU - Wildenberg, Gregg
TI - DRIFT-EM enables direct wafer retrieval of ultrathin serial sections for large-volume electron microscopy
T2 - Cell reports methods
J2 - Cell Rep Methods
PY - 2026
DA - 2026/05/05
VL - 6
IS - 6
SP - 101429
SN - 2667-2375
PB - Elsevier
DO - 10.1016/j.crmeth.2026.101429
UR - https://doi.org/10.1016/j.crmeth.2026.101429
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.crmeth.2026.101429",
"type": "article-journal",
"title": "DRIFT-EM enables direct wafer retrieval of ultrathin serial sections for large-volume electron microscopy",
"container-title": "Cell reports methods",
"author": [
{
"family": "Medina",
"given": "Nelson"
},
{
"family": "Gogola",
"given": "Joseph V."
},
{
"family": "Boergens",
"given": "Kevin"
},
{
"family": "Yang",
"given": "Fuming"
},
{
"family": "Meirovitch",
"given": "Yaron"
},
{
"family": "Lichtman",
"given": "Jeff W."
},
{
"family": "Kasthuri",
"given": "Narayanan"
},
{
"family": "Wildenberg",
"given": "Gregg"
}
],
"container-title-short": "Cell Rep Methods",
"volume": "6",
"issue": "6",
"page": "101429",
"DOI": "10.1016/j.crmeth.2026.101429",
"PMID": "42086049",
"PMCID": "PMC13282653",
"ISSN": "2667-2375",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.crmeth.2026.101429",
"language": "en",
"issued": {
"date-parts": [
[
2026,
5,
5
]
]
}
}

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.1371/journal.pcbi.1013499 [code]
VesiclePy: A machine learning vesicle analysis toolbox for volume electron microscopy.
Journal: PLoS computational biology
In common: OpenCV, scikit-image, Pillow, 4 other tools, histology / microscopy, methods / tools, 4 references
[2] doi:10.3389/fnsys.2026.1822122 [code]
Convergence-divergence circuits for multimodal integration of innate and learned opponent valences.
Journal: Frontiers in systems neuroscience
In common: NetworkX, scikit-image, Pillow, 4 other tools, 4 references
[3] doi:10.1038/s41593-026-02388-9 [code]
Hippocampal CA3 connectomics reveals a gradient of mossy fiber inputs and selective feedforward inhibition onto pyramidal cells.
Journal: Nature neuroscience
In common: NetworkX, OpenCV, Pillow, 4 other tools, histology / microscopy, 3 references
[4] doi:10.1038/s41467-026-72152-x [code]
Centralized brain networks controlling antennal grooming coordination.
Journal: Nature communications
In common: NetworkX, OpenCV, pandas, 3 other tools, 4 references
[5] doi:10.1038/s41586-026-10735-w [code]
Distributed control circuits across a brain-and-cord connectome.
Journal: Nature
In common: NetworkX, scikit-image, pandas, 3 other tools, 4 references
[6] doi:10.1038/s41467-026-72180-7 [code]
Probing molecular diversity and ultrastructure of brain cells with fluorescent aptamers.
Journal: Nature communications
In common: NetworkX, OpenCV, scikit-image, 5 other tools, histology / microscopy, 1 reference
[7] doi:10.1016/j.stemcr.2026.103015 [code]
Brain injury reactivates a developmental program driving genesis and integration of transient LGE-class interneurons.
Journal: Stem cell reports
In common: ImageJ / Fiji, NetworkX, OpenCV, 4 other tools, 1 reference
[8] doi:10.1038/s41598-026-57519-w [code]
Automated segmentation of neurons and spinal cord structures in immunofluorescence images using SpineDL.
Journal: Scientific reports
In common: NetworkX, OpenCV, scikit-image, 5 other tools, histology / microscopy, methods / tools
[9] doi:10.1126/sciadv.aed3650 [code]
Truthful visualizations for mass spectrometry imaging enable high-spatial-resolution interactive &lt;i&gt;m/z&lt;/i&gt; mapping and exploration.
Journal: Science advances
In common: NetworkX, OpenCV, scikit-image, 5 other tools, methods / tools
[10] doi:10.1371/journal.pcbi.1013441 [code]
Large vision model framework for automated C. elegans analysis: From static morphometry to dynamic neural activity.
Journal: PLoS computational biology
In common: NetworkX, OpenCV, scikit-image, 5 other tools, methods / tools

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.