1- import logging
1+ import time
22import warnings
33from contextlib import suppress
44from pathlib import Path
1111from pydantic_core import PydanticSerializationError
1212
1313from microsim ._data_array import ArrayProtocol , from_cache , to_cache
14+ from microsim ._logger import logger , logging_indented
1415from microsim .util import microsim_cache
1516
1617from ._base_model import SimBaseModel
@@ -112,38 +113,46 @@ def ground_truth(self) -> xr.DataArray:
112113 >>> truth.isel(f=0).max('z').plot() # plot max projection of first fluorophore
113114 >>> plt.show()
114115 """
116+ logger .info (f"Creating ground truth (shape ={ self .truth_space .shape } ) + " )
117+ _t0 = time .perf_counter ()
118+
115119 if not hasattr (self , "_ground_truth" ):
116- xp = self ._xp
117- # make empty space into which we'll add the ground truth
118- # TODO: this is wasteful... label.render should probably
119- # accept the space object directly
120- truth = self .truth_space .create (array_creator = xp .zeros )
121-
122- # render each ground truth
123- label_data = []
124- for label in self .sample .labels :
125- cache_path = self ._truth_cache_path (
126- label , self .truth_space , self .settings .random_seed
127- )
128- if self .settings .cache .read and cache_path and cache_path .exists ():
129- data = from_cache (cache_path , xp = xp ).astype (
130- self .settings .float_dtype
131- )
132- logging .info (
133- f"Loaded ground truth for { label } from cache: { cache_path } "
120+ with logging_indented ():
121+ xp = self ._xp
122+ # make empty space into which we'll add the ground truth
123+ # TODO: this is wasteful... label.render should probably
124+ # accept the space object directly
125+ truth = self .truth_space .create (array_creator = xp .zeros )
126+
127+ # render each ground truth
128+ label_data = []
129+ for label in self .sample .labels :
130+ cache_path = self ._truth_cache_path (
131+ label , self .truth_space , self .settings .random_seed
134132 )
135- else :
136- data = label .render (truth , xp = xp )
137- if self .settings .cache .write and cache_path :
138- to_cache (data , cache_path , dtype = np .uint16 )
139-
140- label_data .append (data )
141-
142- # concat along the F axis
143- fluors = [lbl .fluorophore for lbl in self .sample .labels ]
144- truth = xr .concat (label_data , dim = pd .Index (fluors , name = Axis .F ))
145- truth .attrs .update (units = "fluorophores" , long_name = "Ground Truth" )
146- self ._ground_truth = truth
133+ if self .settings .cache .read and cache_path and cache_path .exists ():
134+ data = from_cache (cache_path , xp = xp ).astype (
135+ self .settings .float_dtype
136+ )
137+ logger .info (
138+ f"Loaded ground truth for { label } from cache: { cache_path } "
139+ )
140+ else :
141+ data = label .render (truth , xp = xp )
142+ if self .settings .cache .write and cache_path :
143+ to_cache (data , cache_path , dtype = np .uint16 )
144+
145+ label_data .append (data )
146+
147+ # concat along the F axis
148+ fluors = [lbl .fluorophore for lbl in self .sample .labels ]
149+ truth = xr .concat (label_data , dim = pd .Index (fluors , name = Axis .F ))
150+ truth .attrs .update (units = "fluorophores" , long_name = "Ground Truth" )
151+ self ._ground_truth = truth
152+
153+ logger .info (
154+ f"Ground truth generated in { time .perf_counter () - _t0 :.2f} seconds"
155+ )
147156 return self ._ground_truth
148157
149158 def filtered_emission_rates (self ) -> xr .DataArray :
@@ -234,6 +243,7 @@ def optical_image(self) -> xr.DataArray:
234243 fluorophores in each channel (which a detector would not know). The return
235244 array has dimensions (C, Z, Y, X). The units are photons/s.
236245 """
246+ logger .info ("Creating optical_image ..." )
237247 oipf = self .optical_image_per_fluor ()
238248 return oipf .sum (Axis .F ) # (C, Z, Y, X)
239249
@@ -251,6 +261,9 @@ def digital_image(
251261 are gray values, based on the bit-depth of the detector. If there is no
252262 detector or `with_detector_noise` is False, the units are simply photons.
253263 """
264+ _t0 = time .perf_counter ()
265+ logger .info ("Creating digital_image ..." )
266+
254267 if optical_image is None :
255268 optical_image = self .optical_image ()
256269 image = optical_image # (C, Z, Y, X)
@@ -259,6 +272,7 @@ def digital_image(
259272 # TODO: consider how we would integrate detector pixel size
260273 # rather than a user-sepicified output space
261274 if self .output_space is not None :
275+ logger .info (f"Rescaling to output space { self .output_space .shape } " )
262276 image = self .output_space .rescale (image )
263277
264278 # simulate detector
@@ -276,13 +290,17 @@ def digital_image(
276290 ch_exposures = exposure_ms
277291
278292 if self .detector is not None and with_detector_noise :
293+ logger .info (f"Simulating { type (self .detector ).__name__ } detector ..." )
279294 image = self .detector .render (image , exposure_ms = ch_exposures , xp = self ._xp )
280295 image .attrs .update (units = "gray values" )
281296 else :
282297 image = image * (ch_exposures / 1000 )
283298 image .attrs .update (units = "photons" )
284299
285300 # (C, Z, Y, X)
301+ logger .info (
302+ f"Digital image generated in { time .perf_counter () - _t0 :.2f} seconds"
303+ )
286304 return image
287305
288306 def run (self ) -> xr .DataArray :
0 commit comments