fits2tmp 0.4.0__tar.gz → 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.4
2
+ Name: fits2tmp
3
+ Version: 1.0.2
4
+ Summary: A way of loading FITS files into temporary filesystems to make them available in memory.
5
+ Author: Verity Allan
6
+ Author-email: Verity Allan <vla22@cam.ac.uk>
7
+ License-Expression: BSD-3-Clause
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Requires-Dist: dask[distributed]>=2025.1.0,<2026
15
+ Requires-Dist: astropy>=6.1.4,<7
16
+ Requires-Dist: h5py>=3.16.0
17
+ Requires-Dist: numpy>=2.1.2,<3
18
+ Requires-Dist: pytest>=8.3.3,<9
19
+ Requires-Dist: zarr>=3.4.1
20
+ Requires-Python: >=3.12, <4
21
+ Project-URL: Repository, https://gitlab.developers.cam.ac.uk/phy/ra/vla22-phd/fits2tmp
22
+ Description-Content-Type: text/markdown
23
+
24
+ # fits2tmp
25
+
26
+ This project allows you to take a large FITS file and render it as a set of smaller FITS files stored in-memory in a temporary directory, with appropriately updated headers.
27
+ The program returns a list of the location of these files, which can then be processed by a parallel processing framework such as dask.
28
+
29
+ The program also allows you to turn a zarr file containing your FITS data into small FITS files, provided you have extracted the FITS file header to a text file.
30
+ Utilities in the program can be used to write headers out to or read them from a text file.
31
+
32
+ ## TODO:
33
+
34
+ - Add a utility to convert FITS files to zarr, including saving the header out to a text file.
35
+ - Add hdf5 support using a similar strategy to FITS.
36
+ - Add xradio support.
37
+
38
+ ## Installing fits2tmp
39
+
40
+ The program is available from pypi so can be installed with pip: `pip install fits2tmp`.
41
+
42
+ ## Example Usage
43
+
44
+ ```
45
+ import fits2tmp as f2t
46
+
47
+ a = fakefits2tmpfiles("fits", "/path/to/input/data", "/path/to/temporary/filesystem", "path/to/header/", (100, 100, 100), chunks=(20,20,20))
48
+ ```
49
+
@@ -0,0 +1,26 @@
1
+ # fits2tmp
2
+
3
+ This project allows you to take a large FITS file and render it as a set of smaller FITS files stored in-memory in a temporary directory, with appropriately updated headers.
4
+ The program returns a list of the location of these files, which can then be processed by a parallel processing framework such as dask.
5
+
6
+ The program also allows you to turn a zarr file containing your FITS data into small FITS files, provided you have extracted the FITS file header to a text file.
7
+ Utilities in the program can be used to write headers out to or read them from a text file.
8
+
9
+ ## TODO:
10
+
11
+ - Add a utility to convert FITS files to zarr, including saving the header out to a text file.
12
+ - Add hdf5 support using a similar strategy to FITS.
13
+ - Add xradio support.
14
+
15
+ ## Installing fits2tmp
16
+
17
+ The program is available from pypi so can be installed with pip: `pip install fits2tmp`.
18
+
19
+ ## Example Usage
20
+
21
+ ```
22
+ import fits2tmp as f2t
23
+
24
+ a = fakefits2tmpfiles("fits", "/path/to/input/data", "/path/to/temporary/filesystem", "path/to/header/", (100, 100, 100), chunks=(20,20,20))
25
+ ```
26
+
@@ -0,0 +1,38 @@
1
+ [project]
2
+ name = "fits2tmp"
3
+ version = "1.0.2"
4
+ description = "A way of loading FITS files into temporary filesystems to make them available in memory."
5
+ requires-python = ">=3.12,<4"
6
+ readme = "README.md"
7
+ license = "BSD-3-Clause"
8
+ classifiers = [
9
+ "Programming Language :: Python :: 3",
10
+ "Programming Language :: Python :: 3.10",
11
+ "Programming Language :: Python :: 3.11",
12
+ "Programming Language :: Python :: 3.12",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Programming Language :: Python :: 3.14",
15
+ ]
16
+ dependencies = [
17
+ "dask[distributed]>=2025.1.0,<2026",
18
+ "astropy>=6.1.4,<7",
19
+ "h5py>=3.16.0",
20
+ "numpy>=2.1.2,<3",
21
+ "pytest>=8.3.3,<9",
22
+ "zarr>=3.4.1",
23
+ ]
24
+
25
+ [[project.authors]]
26
+ name = "Verity Allan"
27
+ email = "vla22@cam.ac.uk"
28
+
29
+ [project.urls]
30
+ Repository = "https://gitlab.developers.cam.ac.uk/phy/ra/vla22-phd/fits2tmp"
31
+
32
+ [build-system]
33
+ requires = ["uv_build>=0.13.5,<0.14.0"]
34
+ build-backend = "uv_build"
35
+
36
+ [tool.pytest.ini_options]
37
+ addopts = ["--import-mode=importlib"]
38
+ pythonpath = "src/fits2tmp/"
@@ -0,0 +1,37 @@
1
+ [project]
2
+ name = "fits2tmp"
3
+ version = "1.0.2"
4
+ description = "A way of loading FITS files into temporary filesystems to make them available in memory."
5
+ authors = [{ name = "Verity Allan", email = "vla22@cam.ac.uk" }]
6
+ requires-python = ">=3.12,<4"
7
+ readme = "README.md"
8
+ license = "BSD-3-Clause"
9
+ classifiers = [
10
+ "Programming Language :: Python :: 3",
11
+ "Programming Language :: Python :: 3.10",
12
+ "Programming Language :: Python :: 3.11",
13
+ "Programming Language :: Python :: 3.12",
14
+ "Programming Language :: Python :: 3.13",
15
+ "Programming Language :: Python :: 3.14",
16
+ ]
17
+ dependencies = [
18
+ "dask[distributed]>=2025.1.0,<2026",
19
+ "astropy>=6.1.4,<7",
20
+ "h5py>=3.16.0",
21
+ "numpy>=2.1.2,<3",
22
+ "pytest>=8.3.3,<9",
23
+ "zarr>=3.4.1",
24
+ ]
25
+
26
+ [project.urls]
27
+ Repository = "https://gitlab.developers.cam.ac.uk/phy/ra/vla22-phd/fits2tmp"
28
+
29
+ [build-system]
30
+ requires = ["uv_build>=0.13.5,<0.14.0"]
31
+ build-backend = "uv_build"
32
+
33
+ [tool.pytest.ini_options]
34
+ addopts = [
35
+ "--import-mode=importlib",
36
+ ]
37
+ pythonpath = "src/fits2tmp/"
@@ -0,0 +1,173 @@
1
+ import h5py
2
+ import zarr
3
+ import json
4
+ from astropy.io import fits
5
+ from astropy import wcs
6
+ import dask.array as da
7
+ from dask import delayed
8
+ from dask.distributed import Lock
9
+ import numpy as np
10
+ import os
11
+ import dask
12
+ import tempfile
13
+
14
+ def arr_size_check(source_arr, desired_arr: tuple) -> bool:
15
+ if len(source_arr.shape) < len(desired_arr):
16
+ raise ValueError("You've asked for an array with more dimensions than the array stored")
17
+ else:
18
+ for i in range(min(len(source_arr.shape), len(desired_arr))):
19
+ # using min here allows us to avoid an IndexError when we have more dimensions in the source array
20
+ j = source_arr.shape[i] - desired_arr[i]
21
+ if j < 0:
22
+ raise IndexError("Desired array size is larger than the stored array in at least position %s: source array has dimensions %s, and you have requested $s", i, source_arr.shape, desired_arr)
23
+ return True
24
+
25
+ #def fits_add_header(orig_header, sliced_data) -> fits.Header:
26
+ # coords = wcs.WCS(orig_header)
27
+ # new_header = orig_header
28
+ # new_header.update(coords[data_sli].to_header())
29
+ # return new_header
30
+
31
+ def header_dict(header) -> dict:
32
+ d = {}
33
+ for i in header.cards:
34
+ d[i.keyword] = i.value
35
+ return d
36
+
37
+ def dict2header(input_dict: dict) -> fits.Header:
38
+ nh = fits.Header(cards=input_dict)
39
+ return nh
40
+
41
+ def dict2file(input_dict: dict, filepath):
42
+ with open(filepath, 'w') as f:
43
+ f.write(json.dumps(input_dict))
44
+
45
+ def file2dict(input_file, filepath) -> dict:
46
+ with open(filepath, 'r') as j:
47
+ contents = json.loads(j.read())
48
+ return contents
49
+ #filepath = []
50
+
51
+ def fun(darr, destination, header, block_info=None, filepath=[]):
52
+ arr_loc = block_info[None]['array-location']
53
+ print(arr_loc)
54
+ ch_size = block_info[None]['chunk-shape']
55
+ chunk_loc = block_info[None]['chunk-location']
56
+ coords = []
57
+ for i in range(len(arr_loc)):
58
+ coords.append(slice(arr_loc[i][0],arr_loc[i][1]))
59
+ block_id = str(chunk_loc).replace(',', '-').replace('(', '-').replace(')', '').replace(' ', '')
60
+ n_header = dict2header(header_dict(header))
61
+ wcs_obj = wcs.WCS(n_header)
62
+ n_header.update(wcs_obj[coords].to_header())
63
+ out = fits.PrimaryHDU(data=darr, header=n_header)
64
+ outpath = destination.name + '/' + block_id + ".fits"
65
+ out.writeto(outpath)
66
+ lock = Lock()
67
+ with lock:
68
+ filepath.append(outpath)
69
+ return darr
70
+
71
+ def fits2tmpfiles(inputpath, destination, chunks):
72
+ f = fits.open(inputpath)
73
+ destination = tempfile.TemporaryDirectory(dir=destination, delete=False)
74
+ d = da.from_array(f[0].data, chunks=chunks)
75
+ # TODO overlap
76
+ de = da.overlap.overlap(d, depth=None, boundary=None)
77
+ da.map_blocks(fun, d, destination, f[0].header, dtype=float).compute()
78
+ print(filepath)
79
+
80
+ return filepath
81
+
82
+ def disk2dah(filetype, inputpath, destination, headerloc, arraysize, chunks=None):
83
+ # TODO: ADD code to handle arraysize differing from size of array in file
84
+ # maybe extend arr_size_check()?
85
+ # TODO make this support arrays that have other than 3 dims
86
+ destination = tempfile.TemporaryDirectory(dir=destination, delete=False)
87
+ match filetype:
88
+ case "fits":
89
+ f = fits.open(inputpath)
90
+ d = da.from_array(f[0].data[0:arraysize[0], 0:arraysize[1], 0:arraysize[2]], chunks=chunks)
91
+ n_header = dict2header(header_dict(f[0].header))
92
+ wcs_obj = wcs.WCS(n_header)
93
+ case "zarr":
94
+ d = da.from_zarr(inputpath, chunks=chunks)[0:arraysize, 0:arraysize[1], 0:arraysize[2]]
95
+ n_header = dict2header(file2dict(inputpath, headerloc))
96
+ wcs_obj = wcs.WCS(n_header)
97
+ case "hdf5":
98
+ f = h5py.File(filepath, "r")
99
+ #note this is hardcoded for the particular hdf5 files tested - needs parameterisation
100
+ d = da.from_array(f["array"][arraysize], chunks=chunks)
101
+ n_header = dict2header(file2dict(inputpath, headerloc))
102
+ wcs_obj = wcs.WCS(n_header)
103
+
104
+ case "xradio":
105
+ weeble
106
+ case _:
107
+ raise ValueError("The specified file type is not supported by fits2tmp.")
108
+ return [d, destination, n_header, wcs_obj]
109
+
110
+ def fakefits2tmpfiles(filetype, inputpath, destination, arraysize, headerloc=None, chunks=None, overlap=None, fpaths=[]):
111
+ inputarray = disk2dah(filetype, inputpath, destination, headerloc, arraysize, chunks)
112
+ if overlap == None:
113
+ overlap = [0,0,0]
114
+ darr = inputarray[0]
115
+ print(darr.shape)
116
+ print(darr)
117
+ destination = inputarray[1]
118
+ header = inputarray[2]
119
+ print(header)
120
+ wcs_obj = inputarray[3]
121
+ print(len(darr.chunks))
122
+ # why am I pushing this array to delayed? ans: to parallelise construction of the list
123
+ #dd = darr.to_delayed()
124
+ for i in range(len(darr.chunks[0])):
125
+ x = i * chunks[0] - overlap[0]
126
+ x_delt = (i* chunks[0]) + chunks[0] + overlap[0]
127
+ # range check - when asking for overlap, we curtail at the edges of the array
128
+ if x < 0:
129
+ x = 0
130
+ if x_delt > arraysize[0] -1:
131
+ x_delt = arraysize[0]
132
+ print(x, x_delt)
133
+ for j in range(len(darr.chunks[1])):
134
+ y = j * chunks[1] - overlap[1]
135
+ y_delt = (j * chunks[1]) + chunks[1] + overlap[1]
136
+ if y < 0:
137
+ y = 0
138
+ if y_delt > arraysize[1] -1:
139
+ y_delt = arraysize[1]
140
+ if len(darr.shape) == 2:
141
+ dest = str(i) + str(j)
142
+ data = dd[i][j]
143
+ wcs_coords = wcs_obj.slice((slice(x,x_delt), slice(y,y_delt)))
144
+ elif len(darr.shape) > 2:
145
+ for k in range(len(darr.chunks[2])):
146
+ z = k * chunks[2] - overlap[2]
147
+ z_delt = (k * chunks[2]) + chunks[2] + overlap[2]
148
+ if z < 0:
149
+ z = 0
150
+ if z_delt > arraysize[2] -1:
151
+ z_delt = arraysize[2]
152
+ data = darr[x:x_delt, y:y_delt, z:z_delt]
153
+ print(data.shape)
154
+ dest = '-' + str(i) +'-' + str(j) + '-' + str(k)
155
+ t = wcs_obj[x:x_delt, y:y_delt, z:z_delt]
156
+ header.update(t.to_header())
157
+ hdu = fits.PrimaryHDU(data=data.compute(), header=header)
158
+ hdu.writeto(destination.name + dest + "fits")
159
+ fpaths.append(destination.name + dest + "fits")
160
+ return fpaths
161
+ #def array2disk(array, metadata, outputtype, destination):
162
+
163
+ #def tmpfitsfile2disk(fitsfile, outputtype, destination):
164
+
165
+ #def tmpfitsfile2array(fitsfile, arraytype, arraysize, chunks=None):
166
+
167
+ #def makezarrmetadata(fitsheader):
168
+
169
+ #def makefitsmetadata(fitsheader, location):
170
+
171
+ #makehdf5metadata
172
+
173
+ #makexradiometadata
fits2tmp-0.4.0/LICENSE DELETED
@@ -1,28 +0,0 @@
1
- BSD 3-Clause License
2
-
3
- Copyright (c) 2024, Verity Allan
4
-
5
- Redistribution and use in source and binary forms, with or without
6
- modification, are permitted provided that the following conditions are met:
7
-
8
- 1. Redistributions of source code must retain the above copyright notice, this
9
- list of conditions and the following disclaimer.
10
-
11
- 2. Redistributions in binary form must reproduce the above copyright notice,
12
- this list of conditions and the following disclaimer in the documentation
13
- and/or other materials provided with the distribution.
14
-
15
- 3. Neither the name of the copyright holder nor the names of its
16
- contributors may be used to endorse or promote products derived from
17
- this software without specific prior written permission.
18
-
19
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
fits2tmp-0.4.0/PKG-INFO DELETED
@@ -1,93 +0,0 @@
1
- Metadata-Version: 2.3
2
- Name: fits2tmp
3
- Version: 0.4.0
4
- Summary: A way of loading FITS files into temporary filesystems to make them available in memory.
5
- License: BSD-3-Clause
6
- Author: Verity Allan
7
- Author-email: vla22@cam.ac.uk
8
- Requires-Python: >=3.10,<4.0
9
- Classifier: License :: OSI Approved :: BSD License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
- Requires-Dist: aiohttp (>=3.13.3,<4.0.0)
16
- Requires-Dist: astropy (>=6.1.4,<7.0.0)
17
- Requires-Dist: dask[distributed] (>=2025.1.0,<2026.0.0)
18
- Requires-Dist: fsspec (>=2026.2.0,<2027.0.0)
19
- Requires-Dist: h5py (>=3.12.1,<4.0.0)
20
- Requires-Dist: numpy (>=2.1.2,<3.0.0)
21
- Requires-Dist: pytest (>=8.3.3,<9.0.0)
22
- Requires-Dist: requests (>=2.32.5,<3.0.0)
23
- Requires-Dist: s3fs (>=2026.2.0,<2027.0.0)
24
- Requires-Dist: zarr (>=2.18.3,<3.0.0)
25
- Project-URL: Repository, https://gitlab.developers.cam.ac.uk/phy/ra/vla22-phd/fits2tmp
26
- Description-Content-Type: text/markdown
27
-
28
- # fits2tmp
29
-
30
- This project allows you to take a large FITS file and render it as a set of smaller FITS files stored in-memory in a temporary directory, with appropriately updated headers.
31
- The program returns a list of the location of these files, which can then be processed, by other programs,perhaps by a parallel processing framework such as Dask, or by using Python's `subprocess` module to allow feeding those files to a non-Python program with a command line interface (CLI).
32
-
33
- The program also allows you to turn a zarr file containing your FITS data into small FITS files, provided you have extracted the FITS file header to a text file.
34
-
35
- This can also be done for hdf5 files (again assuming FITS file header data is saved to a text file). Note that if you attempt to do this with Dask running on multiple nodes, it will currently fail, because of a bug in Dask.
36
-
37
- Utilities in the program can be used to write headers out to or read them from a text file.
38
-
39
- ## TODO:
40
-
41
- - Add a utility to convert FITS files to zarr, including saving the header out to a text file.
42
- - Add hdf5 support, using more native hdf5 metadata storage solutions.
43
- - Add xradio/xarray support.
44
-
45
- ## Installing fits2tmp
46
-
47
- The program is available from pypi so can be installed with pip: `pip install fits2tmp`.
48
-
49
- ## Example Usage
50
-
51
- ```
52
- import fits2tmp as f2t
53
-
54
- a = f2t.fitstmpfiles("fits", "/path/to/input/data", "/path/to/temporary/filesystem", "path/to/header/", (100, 100, 100), chunks=(20,20,20))
55
- ```
56
- fitstmpfiles takes a large input data set in one of the supported formats, and returns smaller FITS files to a specified directory.
57
-
58
- The parameters for fitstmpfiles are:
59
-
60
- - filetype: a string describing the filetype; currently supports "fits" and "zarr" fully; "hdf5" only supported on single node runs.
61
- - inputpath: path to the input data
62
- - destination: path to destination directory. It is intended that temporary directories are used, to try to keep the data in-memory, rather than doing potentially expensive disk writes; however, if you wish to write to more permanent storage, that is possible. You will probably want to clean up manually once the run is done, though.
63
- - headerloc: defaults to `None`. If loading from a FITS file, this isn't required; if you are loading from zarr or hdf5, you need to provide the path to a text file containing the FITS headers. It is recommended that you use the fits2tmp utilities to prepare the header file.
64
- - chunks: defaults to `None`. You will want to set this to some value that makes sense for the resources on which you're processing the data. Dask recommend that you have at least 10 times as much RAM as the size of the chunk you're processing. You will also want to consider how much overlap you are using when setting the chunk size. If you are particularly concerned about file loading performance, then consider setting the chunk size to a multiple of the block size used in the storage.
65
- - overlap: defaults to `None`. You will want to set this to some size that makes sense for the data processing you're doing. You probably want to set this to approximately the size of the largest Gaussian kernel you plan to use to detect objects/features.
66
-
67
- ## Utilities
68
-
69
- These small methods are provided to make it easy to write out a FITS header to a file, to allow provision of FITS metadata when using other storage solutions.
70
- To create a header from an existing FITS file:
71
-
72
- ```
73
- from astropy.io import fits
74
- import fits2tmp as f2t
75
-
76
- with fits.open('path/to/fits/file') as f:
77
- header = f[0].header
78
- d = f2t.header_dict(header)
79
- f2t.dict2file(d, 'path/to/where/you/save/header')
80
- ```
81
-
82
- Note that this process strips most of the history from the header; if you wish to update the history, use some of the astropy.io.fits tools to accomplish this. This tool is purely to provide a minimal amount of metadata to allow storing FITS data in zarr and similar file formats, and also to allow processing of sections of FITS files where it's not desirable to have all processes reading from the same file in storage.
83
-
84
- To create a header from text file containing a json dump of a dictionary created from a FITS header:
85
-
86
- ```
87
- import fits2tmp as ft2
88
-
89
- loaded_header_dict = f2t.file2dict('/path/to/header/txt/file')
90
- header = dict2header(loaded_header_dict)
91
- ```
92
- This leaves you with a valid FITS header
93
-
fits2tmp-0.4.0/README.md DELETED
@@ -1,65 +0,0 @@
1
- # fits2tmp
2
-
3
- This project allows you to take a large FITS file and render it as a set of smaller FITS files stored in-memory in a temporary directory, with appropriately updated headers.
4
- The program returns a list of the location of these files, which can then be processed, by other programs,perhaps by a parallel processing framework such as Dask, or by using Python's `subprocess` module to allow feeding those files to a non-Python program with a command line interface (CLI).
5
-
6
- The program also allows you to turn a zarr file containing your FITS data into small FITS files, provided you have extracted the FITS file header to a text file.
7
-
8
- This can also be done for hdf5 files (again assuming FITS file header data is saved to a text file). Note that if you attempt to do this with Dask running on multiple nodes, it will currently fail, because of a bug in Dask.
9
-
10
- Utilities in the program can be used to write headers out to or read them from a text file.
11
-
12
- ## TODO:
13
-
14
- - Add a utility to convert FITS files to zarr, including saving the header out to a text file.
15
- - Add hdf5 support, using more native hdf5 metadata storage solutions.
16
- - Add xradio/xarray support.
17
-
18
- ## Installing fits2tmp
19
-
20
- The program is available from pypi so can be installed with pip: `pip install fits2tmp`.
21
-
22
- ## Example Usage
23
-
24
- ```
25
- import fits2tmp as f2t
26
-
27
- a = f2t.fitstmpfiles("fits", "/path/to/input/data", "/path/to/temporary/filesystem", "path/to/header/", (100, 100, 100), chunks=(20,20,20))
28
- ```
29
- fitstmpfiles takes a large input data set in one of the supported formats, and returns smaller FITS files to a specified directory.
30
-
31
- The parameters for fitstmpfiles are:
32
-
33
- - filetype: a string describing the filetype; currently supports "fits" and "zarr" fully; "hdf5" only supported on single node runs.
34
- - inputpath: path to the input data
35
- - destination: path to destination directory. It is intended that temporary directories are used, to try to keep the data in-memory, rather than doing potentially expensive disk writes; however, if you wish to write to more permanent storage, that is possible. You will probably want to clean up manually once the run is done, though.
36
- - headerloc: defaults to `None`. If loading from a FITS file, this isn't required; if you are loading from zarr or hdf5, you need to provide the path to a text file containing the FITS headers. It is recommended that you use the fits2tmp utilities to prepare the header file.
37
- - chunks: defaults to `None`. You will want to set this to some value that makes sense for the resources on which you're processing the data. Dask recommend that you have at least 10 times as much RAM as the size of the chunk you're processing. You will also want to consider how much overlap you are using when setting the chunk size. If you are particularly concerned about file loading performance, then consider setting the chunk size to a multiple of the block size used in the storage.
38
- - overlap: defaults to `None`. You will want to set this to some size that makes sense for the data processing you're doing. You probably want to set this to approximately the size of the largest Gaussian kernel you plan to use to detect objects/features.
39
-
40
- ## Utilities
41
-
42
- These small methods are provided to make it easy to write out a FITS header to a file, to allow provision of FITS metadata when using other storage solutions.
43
- To create a header from an existing FITS file:
44
-
45
- ```
46
- from astropy.io import fits
47
- import fits2tmp as f2t
48
-
49
- with fits.open('path/to/fits/file') as f:
50
- header = f[0].header
51
- d = f2t.header_dict(header)
52
- f2t.dict2file(d, 'path/to/where/you/save/header')
53
- ```
54
-
55
- Note that this process strips most of the history from the header; if you wish to update the history, use some of the astropy.io.fits tools to accomplish this. This tool is purely to provide a minimal amount of metadata to allow storing FITS data in zarr and similar file formats, and also to allow processing of sections of FITS files where it's not desirable to have all processes reading from the same file in storage.
56
-
57
- To create a header from text file containing a json dump of a dictionary created from a FITS header:
58
-
59
- ```
60
- import fits2tmp as ft2
61
-
62
- loaded_header_dict = f2t.file2dict('/path/to/header/txt/file')
63
- header = dict2header(loaded_header_dict)
64
- ```
65
- This leaves you with a valid FITS header
@@ -1,31 +0,0 @@
1
- [tool.poetry]
2
- name = "fits2tmp"
3
- version = "0.4.0"
4
- description = "A way of loading FITS files into temporary filesystems to make them available in memory."
5
- license = "BSD-3-Clause"
6
- authors = ["Verity Allan <vla22@cam.ac.uk>"]
7
- readme = "README.md"
8
- repository = "https://gitlab.developers.cam.ac.uk/phy/ra/vla22-phd/fits2tmp"
9
-
10
- [tool.poetry.dependencies]
11
- python = "^3.10"
12
- dask = {extras = ["distributed"], version = "^2025.1.0"}
13
- astropy = "^6.1.4"
14
- h5py = "^3.12.1"
15
- numpy = "^2.1.2"
16
- pytest = "^8.3.3"
17
- zarr = "^2.18.3"
18
- fsspec = "^2026.2.0"
19
- requests = "^2.32.5"
20
- aiohttp = "^3.13.3"
21
- s3fs = "^2026.2.0"
22
-
23
- [tool.pytest.ini_options]
24
- addopts = [
25
- "--import-mode=importlib",
26
- ]
27
- pythonpath = "src/fits2tmp/"
28
-
29
- [build-system]
30
- requires = ["poetry-core"]
31
- build-backend = "poetry.core.masonry.api"
@@ -1,192 +0,0 @@
1
- import h5py
2
- import zarr
3
- import json
4
- from astropy.io import fits
5
- from astropy import wcs
6
- import dask.array as da
7
- from dask import delayed
8
- from dask.distributed import Lock
9
- import numpy as np
10
- import os
11
- import dask
12
- import tempfile
13
-
14
- def arr_size_check(source_arr, desired_arr: tuple) -> bool:
15
- if len(source_arr.shape) < len(desired_arr):
16
- raise ValueError("You've asked for an array with more dimensions than the array stored")
17
- else:
18
- for i in range(min(len(source_arr.shape), len(desired_arr))):
19
- # using min here allows us to avoid an IndexError when we have more dimensions in the source array
20
- j = source_arr.shape[i] - desired_arr[i]
21
- if j < 0:
22
- raise IndexError("Desired array size is larger than the stored array in at least position %s: source array has dimensions %s, and you have requested $s", i, source_arr.shape, desired_arr)
23
- return True
24
-
25
- #def fits_add_header(orig_header, sliced_data) -> fits.Header:
26
- # coords = wcs.WCS(orig_header)
27
- # new_header = orig_header
28
- # new_header.update(coords[data_sli].to_header())
29
- # return new_header
30
-
31
- def header_dict(header) -> dict:
32
- d = {}
33
- for i in header.cards:
34
- d[i.keyword] = i.value
35
- return d
36
-
37
- def dict2header(input_dict: dict) -> fits.Header:
38
- nh = fits.Header(cards=input_dict)
39
- return nh
40
-
41
- def dict2file(input_dict: dict, filepath):
42
- with open(filepath, 'w') as f:
43
- f.write(json.dumps(input_dict))
44
-
45
- def file2dict(filepath) -> dict:
46
- with open(filepath, 'r') as j:
47
- contents = json.loads(j.read())
48
- return contents
49
- filepath = []
50
-
51
- def disk2dah(filetype, inputpath, destination, headerloc, arraysize, chunks=None, use_fsspec=False, storage_opts=None):
52
- # TODO: ADD code to handle arraysize differing from size of array in file
53
- # maybe extend arr_size_check()?
54
- # TODO make this support arrays that have other than 3 dims
55
- destination = tempfile.TemporaryDirectory(dir=destination, delete=False)
56
- match filetype:
57
- case "fits":
58
- with fits.open(inputpath, use_fsspec=use_fsspec, fsspec_kwargs=storage_opts) as f:
59
- match len(arraysize):
60
- case 1:
61
- if len(arraysize) == len(f[0].data.shape):
62
- d = da.from_array(f[0].data[0:arraysize[0]], chunks=chunks)
63
- elif len(arraysize) == len(f[0].data.shape) - 1:
64
- d = da.from_array(f[0].data[0:arraysize[0],0], chunks=chunks)
65
- elif len(arraysize) == len(f[0].data.shape) -2:
66
- d = da.from_array(f[0].data[0:arraysize[0],0,0], chunks=chunks)
67
- elif len(arraysize) == len(f[0].data.shape) -3:
68
- d = da.from_array(f[0].data[0:arraysize[0],0,0,0], chunks=chunks)
69
- else:
70
- raise ValueError("You've asked to load an array with more than 4 dimensions, which is not supported.")
71
-
72
- case 2:
73
- if len(arraysize) == len(f[0].data.shape):
74
- d = da.from_array(f[0].data[0:arraysize[0], 0:arraysize[1]], chunks=chunks)
75
- elif len(arraysize) == len(f[0].data.shape) - 1:
76
- d = da.from_array(f[0].data[0:arraysize[0],0:arraysize[1], 0], chunks=chunks)
77
- elif len(arraysize) == len(f[0].data.shape) -2:
78
- d = da.from_array(f[0].data[0:arraysize[0],0:arraysize[1],0,0], chunks=chunks)
79
- else:
80
- raise ValueError("You've asked to load an array with more than 4 dimensions, which is not supported.")
81
- case 3:
82
- if len(arraysize) == len(f[0].data.shape):
83
- d = da.from_array(f[0].data[0:arraysize[0], 0:arraysize[1], 0:arraysize[2]], chunks=chunks)
84
- elif len(arraysize) == len(f[0].data.shape) - 1:
85
- d = da.from_array(f[0].data[0:arraysize[0],0:arrasize[1], 0:arraysize[2],0], chunks=chunks)
86
- else:
87
- raise ValueError("You've asked to load an array with more than 4 dimensions, which is not supported.")
88
- case 4:
89
- d = da.from_array(f[0].data[0:arraysize[0], 0:arraysize[1], 0:arraysize[2], 0:arraysize[3]], chunks=chunks)
90
- n_header = dict2header(header_dict(f[0].header))
91
- wcs_obj = wcs.WCS(n_header)
92
- case "zarr":
93
- if use_fsspec == True:
94
- d = da.from_zarr(inputpath, storage_options=storage_opts, chunks=chunks)[0:arraysize[0],0:arraysize[1],0:arraysize[2]]
95
- print(type(d))
96
- else:
97
- d = da.from_zarr(inputpath, chunks=chunks)[0:arraysize[0],0:arraysize[1],0:arraysize[2]]
98
- n_header = dict2header(file2dict(headerloc))
99
- wcs_obj = wcs.WCS(n_header)
100
- case "hdf5":
101
- with h5py.File(filepath, "r") as f:
102
- #note this is hardcoded for the particular hdf5 files tested - needs parameterisation
103
- d = da.from_array(f["array"][arraysize], chunks=chunks)
104
- n_header = dict2header(file2dict(inputpath, headerloc))
105
- wcs_obj = wcs.WCS(n_header)
106
-
107
- case "xradio":
108
- weeble
109
- case _:
110
- raise ValueError("The specified file type is not supported by fits2tmp.")
111
- return [d, destination, n_header, wcs_obj]
112
-
113
- def fitstmpfiles(filetype, inputpath, destination, arraysize, headerloc=None, chunks=None, overlap=None):
114
- inputarray = disk2dah(filetype, inputpath, destination, headerloc, arraysize, chunks)
115
- if overlap == None:
116
- overlap = [0,0,0]
117
- darr = inputarray[0]
118
- destination = inputarray[1]
119
- header = inputarray[2]
120
- wcs_obj = inputarray[3]
121
- # why am I pushing this array to delayed? ans: to parallelise construction of the list
122
- #dd = darr.to_delayed()
123
- fpaths = []
124
- data = []
125
- for i in range(len(darr.chunks[0])):
126
- x = i * chunks[0] - overlap[0]
127
- x_delt = (i* chunks[0]) + chunks[0] + overlap[0]
128
- # range check - when asking for overlap, we curtail at the edges of the array
129
- if x < 0:
130
- x = 0
131
- if x_delt > arraysize[0] -1:
132
- x_delt = arraysize[0]
133
- if len(darr.shape) == 1:
134
- #can't think of why we want to support 1D array stuff, but it's probably OK to do this, though the headers will end up not very useful, probably
135
- dest = str(i)
136
- data = darr[x:x_delt]
137
- t = wcs_obj[x:x_delt]
138
- header.update(t.to_header())
139
- hdu = fits.PrimaryHDU(data=data, header=header)
140
- hdu.writeto(destination.name + dest + "fits")
141
- fpaths.append(destination.name + dest + "fits")
142
- else:
143
- for j in range(len(darr.chunks[1])):
144
- y = j * chunks[1] - overlap[1]
145
- y_delt = (j * chunks[1]) + chunks[1] + overlap[1]
146
- if y < 0:
147
- y = 0
148
- if y_delt > arraysize[1] -1:
149
- y_delt = arraysize[1]
150
- if len(darr.shape) == 2:
151
- dest = str(i) + str(j)
152
- data = darr[x:x_delt, y:y_delt]
153
- t = wcs_obj[x:x_delt, y:y_delt]
154
- header.update(t.to_header())
155
- hdu = fits.PrimaryHDU(data=data, header=header)
156
- hdu.writeto(destination.name + dest + "fits")
157
- fpaths.append(destination.name + dest + "fits")
158
- elif len(darr.shape) > 2:
159
- for k in range(len(darr.chunks[2])):
160
- z = k * chunks[2] - overlap[2]
161
- z_delt = (k * chunks[2]) + chunks[2] + overlap[2]
162
- if z < 0:
163
- z = 0
164
- if z_delt > arraysize[2] -1:
165
- z_delt = arraysize[2]
166
- dest = '-' + str(i) +'-' + str(j) + '-' + str(k)
167
- if len(darr.shape) == 3:
168
- data = darr[x:x_delt, y:y_delt, z:z_delt]
169
- t = wcs_obj[x:x_delt, y:y_delt, z:z_delt]
170
- header.update(t.to_header())
171
- elif len(darr.shape) == 4:
172
- # this assumes we're dealing with radio astronomy data, so we just select all indices in the 4th axis, which is usually polarisation information
173
- data = darr[x:x_delt, y:y_delt, z:z_delt, :]
174
- t = wcs_obj[x:x_delt, y:y_delt, z:z_delt, :]
175
- header.update(t.to_header())
176
- hdu = fits.PrimaryHDU(data=data, header=header)
177
- hdu.writeto(destination.name + dest + "fits")
178
- fpaths.append(destination.name + dest + "fits")
179
- return fpaths
180
- #def array2disk(array, metadata, outputtype, destination):
181
-
182
- #def tmpfitsfile2disk(fitsfile, outputtype, destination):
183
-
184
- #def tmpfitsfile2array(fitsfile, arraytype, arraysize, chunks=None):
185
-
186
- #def makezarrmetadata(fitsheader):
187
-
188
- #def makefitsmetadata(fitsheader, location):
189
-
190
- #makehdf5metadata
191
-
192
- #makexradiometadata