Skip to content

Commit c1b4b8a

Browse files
committed
2 parents 5905566 + a47b993 commit c1b4b8a

15 files changed

+205
-276
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ McSAS3 (a refactored version of the original McSAS) fits scattering patterns to
2121
6. Some bugs remain. Feel free to add bugs to the issues. They will be fixed as time permits.
2222

2323
## Installation
24-
This package can be installed by ensuring that 1) you have SasModels (pip install sasmodels) and 2) the most recent 21.4+ version of attrs. After that, you can do
24+
This package can be installed by ensuring that 1) you have SasModels (pip install sasmodels) and 2) the most recent 21.4+ version of attrs, as well as pandas. After that, you can do
2525
```git clone https://github.com/BAMresearch/McSAS3.git``` in an appropriate location to install McSAS3
2626
On Windows, if you want to use the sasmodels library, it is highly recommended to run ```pip install tinycc``` so that there's a compatible compiler available.
2727

mcsas3/McData.py

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from ast import Str
2+
from typing import Optional
13
import numpy as np
24
import pandas
35
import h5py
46
from mcsas3.McHDF import McHDF
57
from pathlib import Path
68

9+
# todo use attrs to @define a McData dataclass
710

811
class McData(McHDF):
912
"""
@@ -61,11 +64,11 @@ class McData(McHDF):
6164

6265
def __init__(
6366
self,
64-
df: pandas.DataFrame = None,
65-
loadFromFile: Path = None,
66-
resultIndex=1,
67-
**kwargs,
68-
):
67+
df: Optional[pandas.DataFrame] = None,
68+
loadFromFile: Optional[Path] = None,
69+
resultIndex:int=1,
70+
**kwargs:dict,
71+
)-> None:
6972

7073
# reset everything so we're sure not to inherit anything from elsewhere:
7174
self.filename = None # input filename
@@ -94,16 +97,16 @@ def __init__(
9497
if loadFromFile is not None:
9598
self.load(loadFromFile)
9699

97-
def processKwargs(self, **kwargs):
100+
def processKwargs(self, **kwargs:dict)->None:
98101
for key, value in kwargs.items():
99102
assert key in self.storeKeys, "Key {} is not a valid option".format(key)
100103
setattr(self, key, value)
101104

102-
def linkMeasData(self, measDataLink=None):
105+
def linkMeasData(self, measDataLink:str=None)-> None:
103106
assert False, "defined in 1D and 2D subclasses"
104107
pass
105108

106-
def from_file(self, filename=None):
109+
def from_file(self, filename:Optional[Path]=None)->None:
107110
if filename is None:
108111
assert (
109112
self.filename is not None
@@ -133,15 +136,15 @@ def from_file(self, filename=None):
133136
False
134137
), "Input file type could not be determined. Use from_pandas to load a dataframe or use df = [DataFrame] in input, or use 'loader' = 'from_pdh' or 'from_csv' in input"
135138

136-
def from_pandas(self, df=None):
139+
def from_pandas(self, df:pandas.DataFrame=None)->None:
137140
assert False, "defined in 1D and 2D subclasses"
138141
pass
139142

140-
def from_csv(self, filename=None, csvargs=None):
143+
def from_csv(self, filename:Path=None, csvargs=None)->None:
141144
assert False, "defined in 1D and 2D subclasses"
142145
pass
143146

144-
def from_pdh(self, filename=None):
147+
def from_pdh(self, filename:Path=None)->None:
145148
assert False, "defined in 1D subclass only"
146149
pass
147150

@@ -151,7 +154,7 @@ def from_pdh(self, filename=None):
151154
# pass
152155

153156
# universal reader for 1D and 2D!
154-
def from_nexus(self, filename=None):
157+
def from_nexus(self, filename:Optional[Path]=None)->None:
155158
# optionally, path can be defined as a dict to point at Q, I and ISigma entries.
156159
def objBytesToStr(inObject):
157160
outObject = inObject
@@ -275,22 +278,22 @@ def objBytesToStr(inObject):
275278
self.rawData = pandas.DataFrame(data=self.rawData)
276279
self.prepare()
277280

278-
def is2D(self):
281+
def is2D(self)->bool:
279282
return self.rawData2D is not None
280283

281-
def clip(self):
284+
def clip(self)->None:
282285
assert False, "defined in 1D and 2D subclasses"
283286
pass
284287

285-
def omit(self):
288+
def omit(self)->None:
286289
assert False, "defined in the 1D and (maybe) 2D subclasses"
287290
pass
288291

289-
def reBin(self):
292+
def reBin(self)->None:
290293
assert False, "defined in 1D and 2D subclasses"
291294
pass
292295

293-
def prepare(self):
296+
def prepare(self)->None:
294297
"""runs the clipping and binning (in that order), populates clippedData and binnedData"""
295298
self.clip()
296299
self.omit()
@@ -300,7 +303,7 @@ def prepare(self):
300303
self.binnedData = self.clippedData.copy()
301304
self.linkMeasData()
302305

303-
def store(self, filename=None, path=None):
306+
def store(self, filename:Path, path:Optional[str]=None)->None: # path:str|None
304307
"""stores the settings in an output file (HDF5)"""
305308
if path is None:
306309
path = f"{self.nxsEntryPoint}mcdata/"
@@ -309,7 +312,7 @@ def store(self, filename=None, path=None):
309312
value = getattr(self, key, None)
310313
self._HDFstoreKV(filename=filename, path=path, key=key, value=value)
311314

312-
def load(self, filename: Path = None, path=None):
315+
def load(self, filename: Path, path:Optional[str]=None)->None:
313316
if path is None:
314317
path = f"{self.nxsEntryPoint}mcdata/"
315318
assert filename is not None
@@ -342,24 +345,4 @@ def load(self, filename: Path = None, path=None):
342345
self.from_file() # try loading the data from the original file
343346
self.prepare()
344347

345-
# ### functions to extend the use of McData class to simulated model data
346-
# def polate (self):
347-
# """ Interpolates and extrapolates the data, for use with scale """
348-
# assert False, "defined in 1D or 2D subclass"
349-
# pass
350-
351-
# def interpolate(self, method = None):
352-
# """ Interpolates the data, for use with scale """
353-
# assert False, "defined in 1D or 2D subclass"
354-
# pass
355-
356-
# def scale(self, Rscale:float = 1.):
357-
# """ scales the dataset in Q to "pretend" to be an isoaxial R-scaling"""
358-
# assert False, "defined in 1D or 2D subclass"
359-
# pass
360-
361-
# def extrapolate(self, method = None):
362-
# """ extrapolates the dataset beyond min and max (for use with scale) """
363-
# assert False, "defined in 1D or 2D subclass"
364-
# pass
365348

mcsas3/McData1D.py

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import Optional
12
import numpy as np
23
import pandas
34
from .McData import McData
@@ -14,8 +15,8 @@ class McData1D(McData):
1415
omitQRanges = None # to skip or omit unwanted data ranges, for example with sharp XRD peaks
1516

1617
def __init__(
17-
self, df: pandas.DataFrame = None, loadFromFile: Path = None, resultIndex = 1, **kwargs
18-
):
18+
self, df: Optional[pandas.DataFrame] = None, loadFromFile: Optional[Path] = None, resultIndex:int = 1, **kwargs:dict
19+
)-> None:
1920
super().__init__(loadFromFile=loadFromFile, resultIndex=resultIndex, **kwargs)
2021
self.csvargs = {
2122
"sep": r"\s+",
@@ -38,7 +39,7 @@ def __init__(
3839
self.from_file(self.filename)
3940
# link measData to the requested value
4041

41-
def linkMeasData(self, measDataLink=None):
42+
def linkMeasData(self, measDataLink: Optional[str]=None)->None: # measDataLink:str|None
4243
if measDataLink is None:
4344
measDataLink = self.measDataLink
4445
assert measDataLink in [
@@ -53,7 +54,7 @@ def linkMeasData(self, measDataLink=None):
5354
ISigma=measDataObj.ISigma.values,
5455
)
5556

56-
def from_pdh(self, filename=None):
57+
def from_pdh(self, filename:Path)->None:
5758
"""reads from a PDH file, re-uses Ingo Bressler's code from the notebook example"""
5859
assert filename is not None, "from_pdh requires an input filename of a PDH file"
5960
skiprows, nrows = 5, -1
@@ -65,7 +66,7 @@ def from_pdh(self, filename=None):
6566
csvargs.update({"skiprows": skiprows, "nrows": nrows[0] - skiprows})
6667
self.from_pandas(pandas.read_csv(filename, **csvargs))
6768

68-
def from_pandas(self, df=None):
69+
def from_pandas(self, df:pandas.DataFrame)->None:
6970
"""uses a dataframe as input, should contain 'Q', 'I', and 'ISigma'"""
7071
assert isinstance(
7172
df, pandas.DataFrame
@@ -80,14 +81,14 @@ def from_pandas(self, df=None):
8081
self.rawData = df
8182
self.prepare()
8283

83-
def from_csv(self, filename, csvargs={}):
84+
def from_csv(self, filename:Path, csvargs:dict={})->None:
8485
"""reads from a three-column csv file, takes pandas from_csv arguments"""
8586
assert filename is not None, "from_csv requires an input filename of a csv file"
8687
localCsvargs = self.csvargs.copy()
8788
localCsvargs.update(csvargs)
8889
self.from_pandas(pandas.read_csv(filename, **localCsvargs))
8990

90-
def clip(self):
91+
def clip(self)->None:
9192
self.clippedData = (
9293
self.rawData.query(f"{self.dataRange[0]} <= Q < {self.dataRange[1]}")
9394
.dropna()
@@ -97,7 +98,7 @@ def clip(self):
9798
len(self.clippedData) != 0
9899
), "Data clipping range too small, no datapoints found!"
99100

100-
def omit(self):
101+
def omit(self)->None:
101102
# this can skip/omit unwanted ranges of data (for example a data range with an unwanted XRD peak in it)
102103
# requires an "omitQRanges" list of [[qmin, qmax]]-data ranges to omit
103104

@@ -112,7 +113,7 @@ def omit(self):
112113
inplace=True
113114
)
114115

115-
def reBin(self, nbins=None, IEMin=0.01, QEMin=0.01):
116+
def reBin(self, nbins:Optional[int]=None, IEMin:float=0.01, QEMin:float=0.01) -> None: # nbins:int|None
116117
"""Unweighted rebinning funcionality with extended uncertainty estimation, adapted from the datamerge methods, as implemented in Paulina's notebook of spring 2020"""
117118
if nbins is None:
118119
nbins = self.nbins

mcsas3/McData2D.py

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
from typing import Optional
12
import numpy as np
23
import pandas
34
from .McData import McData
45
import h5py
56
import logging
7+
from pathlib import Path
68

79
class McData2D(McData):
810
"""subclass for managing 2D datasets. Copied from 1D dataset handler, not every functionality is enabled"""
@@ -20,13 +22,9 @@ class McData2D(McData):
2022
0,
2123
] # nudge in direction 0 and 1 in case of misaligned centers. Applied to measData
2224

23-
def __init__(self, df=None, loadFromFile=None, resultIndex=1, **kwargs):
25+
def __init__(self, df=None, loadFromFile=None, resultIndex:int=1, **kwargs:dict)-> None:
2426
super().__init__(resultIndex=resultIndex, **kwargs)
25-
self.csvargs = {
26-
"sep": r"\s+",
27-
"header": None,
28-
"names": ["Q", "I", "ISigma"],
29-
} # default for 1D, overwritten in subclass
27+
self.csvargs = {} # not sure you'd want to load 2D from a CSV.... though I've seen stranger things
3028
self.dataRange = [0, np.inf] # min-max for data range to fit
3129
self.orthoQ1Range = [0, np.inf]
3230
self.orthoQ0Range = [0, np.inf]
@@ -38,11 +36,12 @@ def __init__(self, df=None, loadFromFile=None, resultIndex=1, **kwargs):
3836
self.loader = "from_pandas" # TODO: need to handle this on restore state
3937
self.from_pandas(df)
4038

39+
# TODO not sure why loadFromFile is not used..
4140
elif self.filename is not None: # filename has been set
4241
self.from_file(self.filename)
4342
# link measData to the requested value
4443

45-
def linkMeasData(self, measDataLink=None):
44+
def linkMeasData(self, measDataLink:Optional[str]=None)-> None:
4645
if measDataLink is None:
4746
measDataLink = self.measDataLink
4847
assert measDataLink in [
@@ -60,35 +59,15 @@ def linkMeasData(self, measDataLink=None):
6059
ISigma=measDataObj["ISigma"],
6160
)
6261

63-
def from_pandas(self, df=None):
62+
def from_pandas(self, df:pandas.DataFrame=None)->None:
6463
assert False, "2D data from_pandas not implemented yet"
6564
pass
6665

67-
# """uses a dataframe as input, should contain 'Q', 'I', and 'ISigma'"""
68-
# assert isinstance(
69-
# df, pandas.DataFrame
70-
# ), "from_pandas requires a pandas DataFrame with 'Q', 'I', and 'ISigma'"
71-
# # maybe add a check for the keys:
72-
# assert all(
73-
# [key in df.keys() for key in ["Q", "I", "ISigma"]]
74-
# ), "from_pandas requires the dataframe to contain 'Q', 'I', and 'ISigma'"
75-
# assert all(
76-
# [df[key].dtype.kind in 'f' for key in ["Q", "I", "ISigma"]]
77-
# ), "data could not be read correctly. If csv, did you supply the right csvargs?"
78-
# self.rawData = df
79-
# self.prepare()
80-
81-
def from_csv(self, filename, csvargs={}):
66+
def from_csv(self, filename:Path, csvargs:dict={})->None:
8267
assert False, "2D data from_csv not implemented yet"
8368
pass
8469

85-
# """reads from a three-column csv file, takes pandas from_csv arguments"""
86-
# assert filename is not None, "from_csv requires an input filename of a csv file"
87-
# localCsvargs = self.csvargs.copy()
88-
# localCsvargs.update(csvargs)
89-
# self.from_pandas(pandas.read_csv(filename, **localCsvargs))
90-
91-
def clip(self):
70+
def clip(self) -> None:
9271

9372
# copied from a jupyter notebook:
9473
# test with directly imported data
@@ -162,13 +141,13 @@ def clip(self):
162141
(self.clippedData["Q"][1]).max(),
163142
]
164143

165-
def omit(self):
144+
def omit(self)-> None:
166145
# this can skip/omit unwanted ranges of data (for example a data range with an unwanted XRD peak in it)
167146
# requires an "omitQRanges" list of [[qmin, qmax]]-data ranges to omit
168147
logging.warning("Omitting ranges not implemented yet for 2D")
169148
pass
170149

171-
def reconstruct2D(self, modelI1D):
150+
def reconstruct2D(self, modelI1D: np.ndarray) -> np.ndarray:
172151
"""
173152
Reconstructs a masked 2D data array from the (1D) model intensity, skipping the masked and clipped pixels (left as NaN)
174153
This function can be used to plot the resulting model intensity and comparing it with self.clippedData["I2D"]
@@ -178,7 +157,7 @@ def reconstruct2D(self, modelI1D):
178157
RMI[np.where(self.clippedData["invMask"])] = modelI1D
179158
return RMI
180159

181-
def reBin(self, nbins=None, IEMin=0.01, QEMin=0.01):
160+
def reBin(self, nbins:Optional[int]=None, IEMin:float=0.01, QEMin:float=0.01)->None:
182161
print("2D data rebinning not implemented, binnedData = clippedData for now")
183162
self.binnedData = self.clippedData
184163

mcsas3/McHDF.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ class McHDF(object):
1111
resultIndex = 1 # per default number 1, but can be changed.
1212
nxsEntryPoint = f"/analyses/MCResult{resultIndex}/" # changed to full path to result, not sure if this can work like this
1313

14-
def __init__(self):
14+
def __init__(self)->None:
1515
pass
1616

17-
def _HDFSetResultIndex(self, resultIndex=None):
17+
def _HDFSetResultIndex(self, resultIndex:int)->None:
1818
# resultIndex = -1 should go to the last existing one
1919
#
2020
assert (
@@ -26,7 +26,7 @@ def _HDFSetResultIndex(self, resultIndex=None):
2626
self.resultIndex = resultIndex
2727
self.nxsEntryPoint = f"/analyses/MCResult{self.resultIndex}/"
2828

29-
def _HDFloadKV(self, filename=None, path=None, datatype=None, default=None):
29+
def _HDFloadKV(self, filename:Path, path: str, datatype=None, default=None): # outputs any hdf5 value type
3030
with h5py.File(filename, "r") as h5f:
3131
if path not in h5f:
3232
return default
@@ -86,7 +86,7 @@ def _HDFloadKV(self, filename=None, path=None, datatype=None, default=None):
8686

8787
return value
8888

89-
def _HDFstoreKV(self, filename=None, path=None, key=None, value=None):
89+
def _HDFstoreKV(self, filename:Path, path:str, key:str, value=None)->None:
9090
assert filename is not None, "filename (output filename) cannot be empty"
9191
assert path is not None, "HDF5 path cannot be empty"
9292
assert key is not None, "key cannot be empty"

0 commit comments

Comments
 (0)