Skip to content

Commit eedfa9f

Browse files
authored
feat: version 3.3.0
feat: version 3.3.0
2 parents 454b2cc + d95c43d commit eedfa9f

File tree

14 files changed

+438
-144
lines changed

14 files changed

+438
-144
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,8 @@ _docs
6565
docs/tested_blocks.md
6666
docs/md/all_blocks.rst
6767
docs/md/template.py
68+
69+
## Pycharm
70+
*.xml
71+
.idea
72+

docs/md/utils_blocks.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,13 @@ Utils
1414
Get
1515
Calibration
1616
SortSources
17-
CleanBadPixels
17+
CleanBadPixels
18+
19+
.. currentmodule:: prose.blocks.visualization
20+
21+
.. autosummary::
22+
:template: blocksum.rst
23+
:nosignatures:
24+
25+
Video
26+
VideoPlot

prose/blocks/detection.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@
88
from prose.console_utils import info
99
from prose.core.source import *
1010

11-
__all__ = ["DAOFindStars", "SEDetection", "AutoSourceDetection", "PointSourceDetection"]
11+
__all__ = [
12+
"DAOFindStars",
13+
"SEDetection",
14+
"AutoSourceDetection",
15+
"PointSourceDetection",
16+
"Peaks",
17+
]
1218

1319

1420
class _SourceDetection(Block):
@@ -268,17 +274,24 @@ def __init__(
268274

269275
# TODO: document
270276
class Peaks(Block):
271-
def __init__(self, cutout=11, **kwargs):
277+
def __init__(self, shape=11, **kwargs):
278+
"""Computation of peak values for the detected stars (in ADUs).
279+
280+
Parameters
281+
----------
282+
shape : int, optional
283+
size of the cutout image within which the peak is calculated, by default 11
284+
"""
272285
super().__init__(**kwargs)
273-
self.cutout = cutout
286+
self.shape = shape
274287

275288
def run(self, image, **kwargs):
276-
idxs, cuts = cutouts(image.data, image.sources.coords, size=self.cutout)
277-
peaks = np.ones(len(image.stars_coords)) * -1
278-
for j, i in enumerate(idxs):
279-
cut = cuts[j]
289+
idxs = np.arange(len(image.sources))
290+
peaks = np.ones(len(idxs)) * -1
291+
for j in idxs:
292+
cut = image.cutout(image.sources.coords[j], shape=self.shape)
280293
if cut is not None:
281-
peaks[i] = np.max(cut.data)
294+
peaks[j] = cut.data.max()
282295
image.peaks = peaks
283296

284297

prose/blocks/photometry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def run(self, image: Image):
107107

108108
annulus = image.sources.annulus(rin, rout)
109109
annulus_masks = annulus.to_mask(method="center")
110+
annulus_area = np.pi * (rout**2 - rin**2)
110111

111112
bkg_median = []
112113
for mask in annulus_masks:
@@ -125,4 +126,5 @@ def run(self, image: Image):
125126
"rout": rin,
126127
"median": np.array(bkg_median),
127128
"sigma": self.sigma,
129+
"area": annulus_area,
128130
}

prose/blocks/utils.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -540,12 +540,24 @@ def get_time(im):
540540
def get_aperture(im):
541541
return im.aperture["radii"]
542542

543+
def get_error(im):
544+
_area = np.pi * im.aperture["radii"] ** 2
545+
_signal = im.aperture["fluxes"] - (
546+
im.annulus["median"][:, None] * _area[None, :]
547+
)
548+
# TODO : figure out the correct CCD equation for error computation
549+
_squarred_error = _signal + _area[None, :] * (
550+
im.read_noise**2 + (im.gain / 2) ** 2 + im.annulus["median"][:, None]
551+
)
552+
return np.sqrt(_squarred_error)
553+
543554
super().__init__(
544555
*args,
545556
_time=get_time,
546557
_bkg=get_bkg,
547558
_fluxes=get_fluxes,
548559
_apertures=get_aperture,
560+
_errors=get_error,
549561
name=name,
550562
**kwargs,
551563
)
@@ -564,7 +576,11 @@ def terminate(self):
564576
data = {"bkg": np.mean(self._bkg, -1)}
565577
data.update({key: value for key, value in self.values.items() if key[0] != "_"})
566578
self.fluxes = Fluxes(
567-
time=time, fluxes=raw_fluxes, data=data, apertures=self._apertures
579+
time=time,
580+
fluxes=raw_fluxes,
581+
data=data,
582+
apertures=self._apertures,
583+
errors=self._errors.T,
568584
)
569585

570586

prose/blocks/visualization.py

Lines changed: 129 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -1,145 +1,163 @@
11
import io
2-
import shutil
3-
import tempfile
4-
import time
52

63
import imageio
74
import matplotlib.pyplot as plt
85
import numpy as np
9-
from matplotlib.backends.backend_agg import FigureCanvasAgg
10-
from skimage.transform import resize
11-
12-
from prose import Block, viz
13-
from prose.visualization import corner_text
14-
15-
__all__ = ["VideoPlot"]
16-
17-
18-
def im_to_255(image, factor=0.25):
19-
if factor != 1:
20-
return (
21-
resize(
22-
image.astype(float),
23-
(np.array(np.shape(image)) * factor).astype(int),
24-
anti_aliasing=False,
25-
)
26-
* 255
27-
).astype("uint8")
28-
else:
29-
data = image.copy().astype(float)
30-
data = data / np.max(data)
31-
data = data * 255
32-
return data.astype("uint8")
33-
34-
35-
class _Video(Block):
36-
"""Base block to build a video"""
37-
38-
def __init__(self, destination, fps=10, **kwargs):
39-
super().__init__(**kwargs)
40-
self.destination = destination
41-
self.images = []
42-
self.fps = fps
43-
self.checked_writer = False
446

45-
def run(self, image):
46-
if not self.checked_writer:
47-
_ = imageio.get_writer(self.destination, mode="I")
48-
self.checked_writer = True
7+
from prose.core.block import Block
8+
from prose.utils import z_scale
499

50-
def terminate(self):
51-
imageio.mimsave(self.destination, self.images, fps=self.fps)
10+
__all__ = ["VideoPlot", "Video"]
5211

53-
@property
54-
def citations(self):
55-
return super().citations + ["imageio"]
5612

13+
def im_to_255(image):
14+
data = image.copy().astype(float)
15+
data = data / np.max(data)
16+
data = data * 255
17+
return data.astype("uint8")
5718

58-
class RawVideo(_Video):
19+
20+
class Video(Block):
5921
def __init__(
60-
self, destination, attribute="data", fps=10, function=None, scale=1, **kwargs
22+
self,
23+
destination,
24+
fps=10,
25+
compression=None,
26+
data_function=None,
27+
width=None,
28+
contrast=0.1,
29+
name=None,
6130
):
62-
super().__init__(destination, fps=fps, **kwargs)
63-
if function is None:
31+
"""
32+
A block to create a video from images data (using ffmpeg).
6433
65-
def _donothing(data):
66-
return data
34+
Parameters
35+
----------
36+
destination : str
37+
The path to save the resulting video.
38+
fps : int, optional
39+
The frames per second of the resulting video. Default is 10.
40+
compression : int, optional
41+
The compression rate of the resulting video (-cr value of fmmpeg).
42+
Default is None.
43+
data_function : callable, optional
44+
A function to apply to each image data before adding it to the video.
45+
If none, a z scale is applied to the data with a contrast given by
46+
:code:`contrast`.Default is None.
47+
width : int, optional
48+
The width in pixels of the resulting video.
49+
Default is None (i.e. original image size).
50+
contrast : float, optional
51+
The contrast of the resulting video. Default is 0.1.
52+
Either :code:`contrast` or :code:`data_function` must be provided.
53+
name : str, optional
54+
The name of the block. Default is None.
55+
56+
Attributes
57+
----------
58+
citations : list of str
59+
The citations for the block.
6760
68-
function = _donothing
61+
Methods
62+
-------
63+
run(image)
64+
Adds an image to the video.
65+
terminate()
66+
Closes the video writer.
6967
70-
self.function = function
71-
self.scale = scale
72-
self.attribute = attribute
68+
"""
69+
super().__init__(name=name)
70+
if data_function is None:
71+
72+
def data_function(data):
73+
new_data = data.copy()
74+
new_data = z_scale(new_data, c=contrast)
75+
return new_data
76+
77+
output = []
78+
if compression is not None:
79+
output += ["-crf", f"{compression}"]
80+
if width is not None:
81+
output += ["-vf", f"scale={width}:-1"]
82+
self.writer = imageio.get_writer(
83+
destination,
84+
mode="I",
85+
fps=fps,
86+
output_params=output if len(output) > 0 else None,
87+
)
88+
self.function = data_function
7389

7490
def run(self, image):
75-
super().run(image)
76-
data = self.function(image.__dict__[self.attribute])
77-
self.images.append(im_to_255(data, factor=self.scale))
91+
data = self.function(image.data)
92+
self.writer.append_data(im_to_255(data))
7893

94+
def terminate(self):
95+
self.writer.close()
96+
97+
@property
98+
def citations(self):
99+
return super().citations + ["imageio"]
79100

80-
class VideoPlot(_Video):
81-
def __init__(self, plot_function, destination, fps=10, name=None):
82-
"""Make a video out of a plotting function
101+
102+
class VideoPlot(Video):
103+
def __init__(
104+
self,
105+
plot_function,
106+
destination,
107+
fps=10,
108+
compression=None,
109+
width=None,
110+
name=None,
111+
):
112+
"""
113+
A block to create a video from a matploltib plot (using ffmpeg).
83114
84115
Parameters
85116
----------
86-
plot_function : function
87-
a plotting function taking an :py:class:`prose.Image` as input
88-
destination : str or Path
89-
destination of the video, including extension
117+
plot_function : callable
118+
A function that takes an image as input and produce a plot.
119+
destination : str
120+
The path to save the resulting video.
90121
fps : int, optional
91-
frame per seconds, by default 10
92-
antialias : bool, optional
93-
whether pyplot antialias should be used, by default False
122+
The frames per second of the resulting video. Default is 10.
123+
compression : int, optional
124+
The compression rate of the resulting video (-cr value of fmmpeg).
125+
Default is None.
126+
width : int, optional
127+
The width in pixels of the resulting video.
128+
Default is None (i.e. original image size).
129+
name : str, optional
130+
The name of the block. Default is None.
131+
132+
Attributes
133+
----------
134+
citations : list of str
135+
The citations for the block.
136+
137+
Methods
138+
-------
139+
run(image)
140+
Adds a plot to the video.
141+
terminate()
142+
Closes the video writer.
143+
94144
"""
95-
super().__init__(destination, fps=fps, name=name)
145+
super().__init__(
146+
destination,
147+
fps=fps,
148+
compression=compression,
149+
width=width,
150+
name=name,
151+
)
96152
self.plot_function = plot_function
97-
self.destination = destination
98-
self._temp = tempfile.mkdtemp()
99-
self._images = []
100153

101154
def run(self, image):
102155
self.plot_function(image)
103156
buf = io.BytesIO()
104157
plt.savefig(buf)
105-
self.images.append(imageio.imread(buf))
158+
self.writer.append_data(imageio.imread(buf))
106159
plt.close()
107160

108-
def terminate(self):
109-
super().terminate()
110-
shutil.rmtree(self._temp)
111-
112-
113-
class LivePlot(Block):
114-
def __init__(self, plot_function=None, sleep=0.0, size=None, **kwargs):
115-
super().__init__(**kwargs)
116-
if plot_function is None:
117-
plot_function = lambda im: viz.show_stars(
118-
im.data,
119-
im.stars_coords if hasattr(im, "stars_coords") else None,
120-
size=size,
121-
)
122-
123-
self.plot_function = plot_function
124-
self.sleep = sleep
125-
self.display = None
126-
self.size = size
127-
self.figure_added = False
128-
129-
def run(self, image):
130-
if not self.figure_added:
131-
from IPython import display as disp
132-
133-
self.display = disp
134-
if isinstance(self.size, tuple):
135-
plt.figure(figsize=self.size)
136-
self.figure_added = True
137-
138-
self.plot_function(image)
139-
self.display.clear_output(wait=True)
140-
self.display.display(plt.gcf())
141-
time.sleep(self.sleep)
142-
plt.cla()
143-
144161
def terminate(self):
145162
plt.close()
163+
super().terminate()

0 commit comments

Comments
 (0)