Skip to content

Add numpy array support to initialize_cells for napari integration#4

Merged
Pablo1990 merged 5 commits intomainfrom
copilot/update-initialize-function
Feb 2, 2026
Merged

Add numpy array support to initialize_cells for napari integration#4
Pablo1990 merged 5 commits intomainfrom
copilot/update-initialize-function

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 30, 2026

The initialize_cells() and initialize() methods only accepted file paths, blocking direct integration with napari and other tools that work with in-memory image arrays.

Changes

  • process_image(): Now accepts numpy.ndarray in addition to file paths. Handles both 2D and 3D arrays with appropriate structure elements for scipy.ndimage.label.

  • initialize_cells(): Parameter type broadened from str to str | numpy.ndarray. Generates appropriate cache filenames for array inputs based on shape and settings.

  • initialize(): Added optional img_input parameter accepting arrays or filenames. Maintains backward compatibility with existing None default that uses settings.

  • Binary image support: Automatically segments binary images (max value ≤ 1) using connected component labeling before processing.

Usage

from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import VertexModelVoronoiFromTimeImage

# From napari or other source
image_array = viewer.layers['segmentation'].data  # numpy array

vModel = VertexModelVoronoiFromTimeImage(set_option='wing_disc')
vModel.initialize(image_array)

All existing file-based workflows unchanged.

Original prompt

This section details on the original issue you should resolve

<issue_title>Initialise function should accept an image as a matrix</issue_title>
<issue_description>## 🚀 Feature
To connect napari and pyVertexModel, I need that the function initialize() in vertex model should accept an image either segmented or not segmented and perform initialise the vertex model correctly
</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits January 30, 2026 15:35
Co-authored-by: Pablo1990 <1974224+Pablo1990@users.noreply.github.com>
Co-authored-by: Pablo1990 <1974224+Pablo1990@users.noreply.github.com>
Copilot AI changed the title [WIP] Add image matrix support to initialize function Add numpy array support to initialize_cells for napari integration Jan 30, 2026
Copilot AI requested a review from Pablo1990 January 30, 2026 15:54
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 2, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Comment @coderabbitai help to get the list of available commands and usage tips.

@Pablo1990 Pablo1990 marked this pull request as ready for review February 2, 2026 14:24
Copilot AI review requested due to automatic review settings February 2, 2026 14:24
@Pablo1990 Pablo1990 merged commit 665c922 into main Feb 2, 2026
4 of 5 checks passed
@Pablo1990 Pablo1990 deleted the copilot/update-initialize-function branch February 2, 2026 14:24
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for initializing the vertex model directly from in-memory numpy.ndarray images (e.g., napari layers), instead of requiring file paths.

Changes:

  • Broadened process_image() / initialize_cells() / initialize() inputs to accept numpy.ndarray as well as filenames.
  • Added basic binary-image handling via connected-component labeling for array inputs.
  • Updated caching / screenshot handling paths for array-based initialization, plus added new tests for array inputs.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 9 comments.

File Description
src/pyVertexModel/geometry/geo.py Moves build_global_ids() earlier in build_cells() before initializing reference values.
src/pyVertexModel/algorithm/vertexModelVoronoiFromTimeImage.py Adds ndarray support to process_image(), initialize_cells(), and threading img_input through obtain_initial_x_and_tetrahedra().
src/pyVertexModel/algorithm/vertexModel.py Extends initialize() to accept an optional img_input passed down to initialize_cells().
Tests/test_vertexModel.py Updates imports and adds tests intended to cover ndarray-based initialization and processing.
Comments suppressed due to low confidence (15)

src/pyVertexModel/geometry/geo.py:766

  • This comment appears to contain commented-out code.
        # for c in range(self.nCells):
        #    self.Cells[c].cglobalIds = c + self.numY + self.numF

src/pyVertexModel/geometry/geo.py:1028

  • This comment appears to contain commented-out code.
        # if Set.contributionOldYs == 0:
        #     new_tets = np.array([cell.compute_y(self, Tnew[numTet, :], self.Cells[mainNodesToConnect].X, Set)
        #                          for numTet in range(Tnew.shape[0])])
        #     for new_tet_id, new_tet in enumerate(new_tets):
        #         # Adjust Z of the new_tets
        #         if any(np.isin(Tnew[new_tet_id], self.XgTop)):
        #             tets_to_use = np.any(np.isin(self.Cells[mainNodesToConnect].T, self.XgTop), axis=1)
        #         elif any(np.isin(Tnew[new_tet_id], self.XgBottom)):
        #             tets_to_use = np.any(np.isin(self.Cells[mainNodesToConnect].T, self.XgBottom), axis=1)
        #         else:
        #             return new_tets
        #
        #         new_tet[2] = np.mean(self.Cells[mainNodesToConnect].Y[tets_to_use], axis=0)[2]
        #
        #     return new_tets

src/pyVertexModel/geometry/geo.py:1265

  • This comment appears to contain commented-out code.
                    # for tet_id, tet in enumerate(cell.T):
                    #     if (np.sum(np.isin(tet, regular_cells)) == 2 and np.all(np.isin(self.cellsToAblate, tet))
                    #             and cell_ids_domain[tet_id]):
                    #         y_ablated.append(cell.globalIds[tet_id])

src/pyVertexModel/geometry/geo.py:1519

  • This comment appears to contain commented-out code.
        # for c_cell in self.Cells:
        #     if c_cell.AliveStatus == 1 and c_cell.ID not in self.BorderCells:
        #         if location_filter == 'Top':
        #             ref_z_values.append(np.mean(c_cell.Y[np.any(np.isin(c_cell.T, self.XgTop), axis=1), 2]))
        #         elif location_filter == 'Bottom':
        #             ref_z_values.append(np.mean(c_cell.Y[np.any(np.isin(c_cell.T, self.XgBottom), axis=1), 2]))

src/pyVertexModel/geometry/geo.py:2206

  • This comment appears to contain commented-out code.
            #for vertex in vertices_to_equidistant_move:
            #    distances.append(compute_distance_3d(extreme_of_edge_ys[1], vertex))

src/pyVertexModel/algorithm/vertexModel.py:275

  • This comment appears to contain commented-out code.
            # if os.path.getmtime(output_filename) < (time.time() - 24 * 60 * 60):
            #     logger.info(f'Redoing the file {output_filename} as it is older than 1 day')
            # else:

src/pyVertexModel/algorithm/vertexModel.py:755

  • This comment appears to contain commented-out code.
        # except Exception as e:
        #     logger.error(f"Error while computing wound features: {e}")

src/pyVertexModel/algorithm/vertexModel.py:1134

  • This comment appears to contain commented-out code.
                #except Exception as e:
                #    logger.error(f'Error while running the iteration for purse string strength: {e}')
                #    return np.inf, np.inf, np.inf, np.inf

src/pyVertexModel/algorithm/vertexModelVoronoiFromTimeImage.py:421

  • This comment appears to contain commented-out code.
            # if os.path.getmtime(output_filename) < (time.time() - 24 * 60 * 60):
            #     logger.info(f'Redoing the file {output_filename} as it is older than 1 day')
            # else:

src/pyVertexModel/algorithm/vertexModelVoronoiFromTimeImage.py:479

  • This comment appears to contain commented-out code.
            #     for c_cell in main_cells:
            #         for c_neighbour in img_neighbours[c_cell]:
            #             if len(main_cells) >= total_cells:
            #                 break
            #
            #             if c_neighbour not in main_cells:
            #                 main_cells = np.append(main_cells, c_neighbour)

src/pyVertexModel/geometry/geo.py:551

  • Variable avg_faces is not used.
        avg_faces = np.mean(number_of_faces_per_cell_only_top_and_bottom)

src/pyVertexModel/geometry/geo.py:2008

  • Variable original_tets is not used.
            original_tets = [i for i, tet in enumerate(tets) if original in tet]

Tests/test_vertexModel.py:294

  • Variable x is not used.
        x = mat_info['X']

src/pyVertexModel/geometry/geo.py:1365

  • Except block directly handles BaseException.
        except:

src/pyVertexModel/algorithm/vertexModel.py:95

  • This statement is unreachable.
        z_coordinate = [cell_height, -cell_height]

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +242 to +244
imgStackLabelled, num_features = label(imgStackLabelled == 0,
structure=structure_element)

Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the numpy-array branch, imgStackLabelled gets overwritten by label(imgStackLabelled == 0, ...). This discards any existing label image (labels > 0) and also overwrites the result of the binary connected-component labeling done earlier, so the returned labels will represent connected background regions instead of cells. Adjust the logic to only run label(...==0) when the input is an intensity/boundary image, and otherwise keep/relabel the existing segmentation (e.g., run regionprops/renumber on the current label image rather than relabeling background).

Copilot uses AI. Check for mistakes.
Comment on lines +434 to +439
image_file = '/'+ os.path.join(*img_input.split('/')[:-1])
screenshot_(self.geo, self.set, 0, output_filename.split('/')[-1], image_file)
else:
# For numpy array input, try to save screenshot in output folder if available
if hasattr(self.set, 'OutputFolder') and self.set.OutputFolder:
screenshot_(self.geo, self.set, 0, output_filename.split('/')[-1], self.set.OutputFolder)
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Path handling here is not cross-platform: splitting on '/' and prefixing with '/' will break on Windows (this repo’s tox config runs windows). Use os.path.dirname(img_input) / os.path.basename(output_filename) (or pathlib.Path) instead of manual string splitting.

Suggested change
image_file = '/'+ os.path.join(*img_input.split('/')[:-1])
screenshot_(self.geo, self.set, 0, output_filename.split('/')[-1], image_file)
else:
# For numpy array input, try to save screenshot in output folder if available
if hasattr(self.set, 'OutputFolder') and self.set.OutputFolder:
screenshot_(self.geo, self.set, 0, output_filename.split('/')[-1], self.set.OutputFolder)
# Use os.path to get the directory of the input image in a cross-platform way
image_file = os.path.dirname(img_input) or "."
screenshot_(self.geo, self.set, 0, os.path.basename(output_filename), image_file)
else:
# For numpy array input, try to save screenshot in output folder if available
if hasattr(self.set, 'OutputFolder') and self.set.OutputFolder:
screenshot_(self.geo, self.set, 0, os.path.basename(output_filename), self.set.OutputFolder)

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_vertexModel.py
Comment on lines +15 to +17
from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import build_triplets_of_neighs, \
VertexModelVoronoiFromTimeImage, add_tetrahedral_intercalations, \
get_four_fold_vertices, divide_quartets_neighbours, process_image
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import list was trimmed, but this test module still calls calculate_neighbours, build_2d_voronoi_from_image, populate_vertices_info, and calculate_vertices later in the file. Those names are now undefined, so the test suite will fail with NameError. Either restore the needed imports or update the tests to call these via a VertexModelVoronoiFromTimeImage instance (if they are instance methods).

Suggested change
from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import build_triplets_of_neighs, \
VertexModelVoronoiFromTimeImage, add_tetrahedral_intercalations, \
get_four_fold_vertices, divide_quartets_neighbours, process_image
from src.pyVertexModel.algorithm.vertexModelVoronoiFromTimeImage import build_triplets_of_neighs, calculate_neighbours, \
build_2d_voronoi_from_image, populate_vertices_info, calculate_vertices, VertexModelVoronoiFromTimeImage, \
add_tetrahedral_intercalations, get_four_fold_vertices, divide_quartets_neighbours, process_image

Copilot uses AI. Check for mistakes.
Comment on lines +290 to +293
if img_input is None:
self.initialize_cells(filename)
else:
self.initialize_cells(img_input)
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When img_input is provided, initialization correctly calls initialize_cells(img_input), but the earlier cache check (exists(output_filename)) is still based on self.set.initial_filename_state rather than the provided image. This means passing an array can be ignored if a cached state exists for the settings file. Consider bypassing the cache or incorporating img_input into the cache key when img_input is not None.

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +302
imgStackLabelled, num_features = label(imgStackLabelled == 0,
structure=[[0, 1, 0], [1, 1, 1], [0, 1, 0]])
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the filename (.tif) branch, scipy.ndimage.label is called with a 2D structure even when imgStackLabelled.ndim == 3. label requires structure to have the same rank as the input, so 3D TIFF stacks will error here. Consider using the same dimension-aware structure_element logic as the numpy-array branch when imgStackLabelled.ndim == 3.

Suggested change
imgStackLabelled, num_features = label(imgStackLabelled == 0,
structure=[[0, 1, 0], [1, 1, 1], [0, 1, 0]])
# Choose a connectivity structure with the same dimensionality as the image stack
if imgStackLabelled.ndim == 3:
# 3D cross-shaped connectivity (analogous to the 2D cross below)
structure_element = np.zeros((3, 3, 3), dtype=int)
structure_element[1, :, 1] = 1
structure_element[:, 1, 1] = 1
structure_element[1, 1, :] = 1
else:
# 2D cross-shaped connectivity
structure_element = np.array([[0, 1, 0],
[1, 1, 1],
[0, 1, 0]], dtype=int)
imgStackLabelled, num_features = label(imgStackLabelled == 0,
structure=structure_element)

Copilot uses AI. Check for mistakes.
Comment on lines +409 to +413
# Input is a numpy array - create a generic filename based on array shape and settings
output_filename = f'vertex_model_array_{img_input.shape}_{self.set.TotalCells}cells_{self.set.CellHeight}.pkl'
# Save it in the output folder if available, otherwise in current directory
if hasattr(self.set, 'OutputFolder') and self.set.OutputFolder:
output_filename = os.path.join(self.set.OutputFolder, output_filename)
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For numpy array inputs, the cache filename is derived only from img_input.shape and a couple settings values. Different images with the same shape/settings will collide and cause initialize_cells to load an unrelated cached state. Consider including a content hash (e.g., sha256 of the array bytes + dtype) or disabling cache reuse for in-memory inputs unless an explicit cache key/name is provided.

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_vertexModel.py
Comment on lines +516 to +520
set_test = Set(set_option='voronoi_from_image')
set_test.TotalCells = 50 # Use fewer cells for faster testing
set_test.CellHeight = 10

# Test with numpy array input
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Set does not accept a set_option keyword argument (its constructor takes only mat_file=None). This will raise TypeError and fail the test. Create Set() and then set the needed attributes (and call set_test.update_derived_parameters() if required), or construct the model using VertexModelVoronoiFromTimeImage(set_option=..., set_test=None) with a valid Set preset method name.

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_vertexModel.py
img_2d = img_array[:, :, 0]

# Create settings
set_test = Set(set_option='voronoi_from_image')
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue here: Set(set_option='voronoi_from_image') is not a valid constructor call for Set and will raise TypeError. Use Set() + attribute overrides (and update derived parameters) instead.

Suggested change
set_test = Set(set_option='voronoi_from_image')
set_test = Set()
set_test.set_option = 'voronoi_from_image'

Copilot uses AI. Check for mistakes.
Comment thread Tests/test_vertexModel.py
Comment on lines +535 to +548
# Create a simple labeled image
test_img = np.zeros((100, 100), dtype=np.uint16)
# Create some labeled regions
test_img[10:30, 10:30] = 1
test_img[40:60, 40:60] = 2
test_img[70:90, 70:90] = 3

# Test process_image with numpy array
img2d, img_stack = process_image(test_img)

# Verify the output
assert img2d is not None, "2D image should be returned"
assert img_stack is not None, "Image stack should be returned"
assert img2d.shape == test_img.shape, "2D image should have same shape as input"
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test only asserts that shapes are returned, so it won’t catch incorrect relabeling (e.g., returning labels for background regions instead of the input labeled regions). Add assertions that the labeled regions remain labeled after process_image (e.g., pixels in the three squares stay non-zero and distinct, and background stays 0).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Initialise function should accept an image as a matrix

3 participants