@@ -142,3 +142,170 @@ def test_origin(self, grid, ccp4data):
142142
143143 def test_data (self , grid , ccp4data ):
144144 assert_allclose (grid .grid , ccp4data .array )
145+
146+ class TestMRCWrite :
147+ """Tests for MRC write functionality"""
148+
149+ def test_mrc_write_roundtrip (self , ccp4data , tmpdir ):
150+ """Test writing and reading back preserves data"""
151+ outfile = str (tmpdir / "roundtrip.mrc" )
152+
153+ # Write the file
154+ ccp4data .write (outfile )
155+
156+ # Read it back
157+ mrc_read = mrc .MRC (outfile )
158+
159+ # Check data matches
160+ assert_allclose (mrc_read .array , ccp4data .array )
161+ assert_allclose (mrc_read .origin , ccp4data .origin , rtol = 1e-5 , atol = 1e-3 )
162+ assert_allclose (mrc_read .delta , ccp4data .delta , rtol = 1e-5 )
163+
164+ def test_mrc_write_header_preserved (self , ccp4data , tmpdir ):
165+ """Test that header fields are preserved"""
166+ outfile = str (tmpdir / "header.mrc" )
167+
168+ ccp4data .write (outfile )
169+ mrc_read = mrc .MRC (outfile )
170+
171+ # Check axis ordering preserved
172+ assert mrc_read .header .mapc == ccp4data .header .mapc
173+ assert mrc_read .header .mapr == ccp4data .header .mapr
174+ assert mrc_read .header .maps == ccp4data .header .maps
175+
176+ # Check offsets preserved
177+ assert mrc_read .header .nxstart == ccp4data .header .nxstart
178+ assert mrc_read .header .nystart == ccp4data .header .nystart
179+ assert mrc_read .header .nzstart == ccp4data .header .nzstart
180+
181+ def test_mrc_write_new_file (self , tmpdir ):
182+ """Test creating new MRC file from scratch"""
183+ outfile = str (tmpdir / "new.mrc" )
184+
185+ # Create new MRC object
186+ mrc_new = mrc .MRC ()
187+ mrc_new .array = np .arange (24 ).reshape (2 , 3 , 4 ).astype (np .float32 )
188+ mrc_new .delta = np .diag ([1.0 , 2.0 , 3.0 ])
189+ mrc_new .origin = np .array ([5.0 , 10.0 , 15.0 ])
190+ mrc_new .rank = 3
191+
192+ # Write and read back
193+ mrc_new .write (outfile )
194+ mrc_read = mrc .MRC (outfile )
195+
196+ # Verify
197+ assert_allclose (mrc_read .array , mrc_new .array , rtol = 1e-5 )
198+ assert_allclose (mrc_read .origin , mrc_new .origin , rtol = 1e-4 )
199+ assert_allclose (np .diag (mrc_read .delta ), np .diag (mrc_new .delta ), rtol = 1e-5 )
200+
201+ def test_mrc_write_zero_voxel_raises (self , tmpdir ):
202+ """Test that zero voxel size raises ValueError"""
203+ outfile = str (tmpdir / "invalid.mrc" )
204+
205+ mrc_obj = mrc .MRC ()
206+ mrc_obj .array = np .ones ((2 , 2 , 2 ), dtype = np .float32 )
207+ mrc_obj .delta = np .diag ([0.0 , 1.0 , 1.0 ])
208+ mrc_obj .origin = np .array ([0.0 , 0.0 , 0.0 ])
209+ mrc_obj .rank = 3
210+
211+ with pytest .raises (ValueError , match = "Voxel size must be positive" ):
212+ mrc_obj .write (outfile )
213+
214+
215+ class TestGridMRCWrite :
216+ """Tests for Grid.export() with MRC format"""
217+
218+ def test_grid_export_mrc (self , tmpdir ):
219+ """Test Grid.export() with file_format='mrc'"""
220+ outfile = str (tmpdir / "grid.mrc" )
221+
222+ # Create simple grid
223+ data = np .arange (60 ).reshape (3 , 4 , 5 ).astype (np .float32 )
224+ g = Grid (data , origin = [0 , 0 , 0 ], delta = [1.0 , 1.0 , 1.0 ])
225+
226+ # Export and read back
227+ g .export (outfile , file_format = 'mrc' )
228+ g_read = Grid (outfile )
229+
230+ # Verify
231+ assert_allclose (g_read .grid , g .grid , rtol = 1e-5 )
232+ assert_allclose (g_read .origin , g .origin , rtol = 1e-4 )
233+ assert_allclose (g_read .delta , g .delta , rtol = 1e-5 )
234+
235+ def test_grid_export_mrc_roundtrip (self , tmpdir ):
236+ """Test MRC → Grid → export → Grid preserves data"""
237+ outfile = str (tmpdir / "roundtrip_grid.mrc" )
238+
239+ # Load original
240+ g_orig = Grid (datafiles .CCP4_1JZV )
241+
242+ # Export and reload
243+ g_orig .export (outfile , file_format = 'mrc' )
244+ g_read = Grid (outfile )
245+
246+ # Verify
247+ assert_allclose (g_read .grid , g_orig .grid , rtol = 1e-5 )
248+ assert_allclose (g_read .origin , g_orig .origin , rtol = 1e-4 )
249+ assert_allclose (g_read .delta , g_orig .delta , rtol = 1e-5 )
250+ assert_equal (g_read .grid .shape , g_orig .grid .shape )
251+
252+ def test_grid_export_mrc_preserves_header (self , tmpdir ):
253+ """Test that Grid preserves MRC header through export"""
254+ outfile = str (tmpdir / "header_grid.mrc" )
255+
256+ g_orig = Grid (datafiles .CCP4_1JZV )
257+ orig_mapc = g_orig ._mrc_header .mapc
258+ orig_mapr = g_orig ._mrc_header .mapr
259+ orig_maps = g_orig ._mrc_header .maps
260+
261+ # Export and check
262+ g_orig .export (outfile , file_format = 'mrc' )
263+ g_read = Grid (outfile )
264+
265+ assert g_read ._mrc_header .mapc == orig_mapc
266+ assert g_read ._mrc_header .mapr == orig_mapr
267+ assert g_read ._mrc_header .maps == orig_maps
268+
269+ def test_mrc_write_4x4x4_with_header (self , tmpdir ):
270+ """Test writing 4x4x4 MRC file with custom header values."""
271+
272+ # Create 4x4x4 data
273+ data = np .arange (64 , dtype = np .float32 ).reshape ((4 , 4 , 4 ))
274+ outfile = str (tmpdir / "test_with_header.mrc" )
275+
276+ # Create and write MRC
277+ m = mrc .MRC ()
278+ m .array = data
279+ m .delta = np .diag ([1.5 , 2.0 , 2.5 ])
280+ m .origin = np .array ([10.0 , 20.0 , 30.0 ])
281+ m .rank = 3
282+ m .write (outfile )
283+
284+ # Read back and verify
285+ m_read = mrc .MRC (outfile )
286+ assert_allclose (m_read .array , data )
287+ assert_allclose (np .diag (m_read .delta ), [1.5 , 2.0 , 2.5 ])
288+ assert_allclose (m_read .origin , [10.0 , 20.0 , 30.0 ], rtol = 1e-4 , atol = 1.0 )
289+
290+
291+ def test_mrc_write_4x4x4_without_header (self , tmpdir ):
292+ """Test writing 4x4x4 MRC file with default header."""
293+
294+ # Create 4x4x4 random data
295+ np .random .seed (42 )
296+ data = np .random .rand (4 , 4 , 4 ).astype (np .float32 )
297+ outfile = str (tmpdir / "test_without_header.mrc" )
298+
299+ # Create and write MRC
300+ m = mrc .MRC ()
301+ m .array = data
302+ m .delta = np .diag ([1.0 , 1.0 , 1.0 ])
303+ m .origin = np .array ([0.0 , 0.0 , 0.0 ])
304+ m .rank = 3
305+ m .write (outfile )
306+
307+ # Read back and verify
308+ m_read = mrc .MRC (outfile )
309+ assert_allclose (m_read .array , data )
310+ assert_allclose (np .diag (m_read .delta ), [1.0 , 1.0 , 1.0 ])
311+ assert_allclose (m_read .origin , [0.0 , 0.0 , 0.0 ], rtol = 1e-4 , atol = 1.0 )
0 commit comments