ccfx 1.0.8__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- ccfx/ccfx.py +273 -19
- {ccfx-1.0.8.dist-info → ccfx-1.1.0.dist-info}/METADATA +4 -1
- {ccfx-1.0.8.dist-info → ccfx-1.1.0.dist-info}/RECORD +6 -6
- {ccfx-1.0.8.dist-info → ccfx-1.1.0.dist-info}/WHEEL +0 -0
- {ccfx-1.0.8.dist-info → ccfx-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {ccfx-1.0.8.dist-info → ccfx-1.1.0.dist-info}/top_level.txt +0 -0
ccfx/ccfx.py
CHANGED
|
@@ -22,7 +22,6 @@ import platform
|
|
|
22
22
|
import zipfile
|
|
23
23
|
import pickle
|
|
24
24
|
import time
|
|
25
|
-
from shapely.geometry import box, Point
|
|
26
25
|
import geopandas, pandas
|
|
27
26
|
from collections import defaultdict
|
|
28
27
|
import py7zr
|
|
@@ -38,6 +37,12 @@ import yt_dlp
|
|
|
38
37
|
from typing import Optional, Any
|
|
39
38
|
from datetime import datetime, timedelta
|
|
40
39
|
from PIL import Image
|
|
40
|
+
import scipy as scipy
|
|
41
|
+
from shapely.geometry import LineString, Polygon, MultiPolygon, box, Point
|
|
42
|
+
from shapely.ops import polygonize, unary_union
|
|
43
|
+
import rasterio
|
|
44
|
+
from rasterio import features
|
|
45
|
+
from rasterio.transform import from_bounds
|
|
41
46
|
|
|
42
47
|
# functions
|
|
43
48
|
def listFiles(path: str, ext: Optional[str] = None) -> list:
|
|
@@ -479,9 +484,9 @@ def alert(message:str, server:str = "http://ntfy.sh", topic:str = "pythonAlerts"
|
|
|
479
484
|
return: True if the alert was sent successfully, False otherwise
|
|
480
485
|
'''
|
|
481
486
|
print(message) if printIt else None; header_data = {}
|
|
482
|
-
if not messageTitle is None: header_data["Title"] = messageTitle
|
|
483
|
-
if not priority is None: header_data["Priority"] = priority
|
|
484
|
-
if not len(tags) == 0: header_data["Tags"] = ",".join(tags)
|
|
487
|
+
if not messageTitle is None: header_data["Title"] = str(messageTitle).replace("\r"," ").replace("\n"," ")
|
|
488
|
+
if not priority is None: header_data["Priority"] = str(int(priority))
|
|
489
|
+
if not len(tags) == 0: header_data["Tags"] = ",".join(map(str, tags))
|
|
485
490
|
|
|
486
491
|
try:
|
|
487
492
|
if v: print(f"sending alert to {server}/{topic}")
|
|
@@ -517,6 +522,7 @@ def deletePath(path:str, v:bool = False) -> bool:
|
|
|
517
522
|
if v:
|
|
518
523
|
print(f'! {path} does not exist')
|
|
519
524
|
deleted = False
|
|
525
|
+
return deleted
|
|
520
526
|
|
|
521
527
|
|
|
522
528
|
def downloadChunk(url: str, start: int, end: int, path: str) -> None:
|
|
@@ -528,6 +534,54 @@ def downloadChunk(url: str, start: int, end: int, path: str) -> None:
|
|
|
528
534
|
f.write(chunk)
|
|
529
535
|
|
|
530
536
|
|
|
537
|
+
def correctFisheye(inputFile: str, outputFile: str = '',
|
|
538
|
+
k1: float = -0.1, k2: float = 0.05,
|
|
539
|
+
cx: float = 0.5, cy: float = 0.5,
|
|
540
|
+
crf: int = 20) -> str:
|
|
541
|
+
"""
|
|
542
|
+
Correct fisheye distortion in a video and save as MP4.
|
|
543
|
+
|
|
544
|
+
Args:
|
|
545
|
+
inputFile (str): Path to the input video (any format).
|
|
546
|
+
outputFile (str, optional): Path for the corrected MP4. If None, auto-creates.
|
|
547
|
+
k1, k2 (float): Lens distortion coefficients.
|
|
548
|
+
cx, cy (float): Optical center (0.5 = image center).
|
|
549
|
+
crf (int): Constant Rate Factor (lower = better quality, larger file).
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
str: Path to the corrected MP4 file.
|
|
553
|
+
"""
|
|
554
|
+
if not os.path.exists(inputFile):
|
|
555
|
+
raise FileNotFoundError(f"Input file not found: {inputFile}")
|
|
556
|
+
|
|
557
|
+
# Default output path
|
|
558
|
+
if outputFile == '':
|
|
559
|
+
base, _ = os.path.splitext(inputFile)
|
|
560
|
+
outputFile = f"{base}_corrected.mp4"
|
|
561
|
+
|
|
562
|
+
cmd = [
|
|
563
|
+
"ffmpeg", "-hide_banner", "-loglevel", "error", "-stats",
|
|
564
|
+
"-i", inputFile,
|
|
565
|
+
"-vf", f"lenscorrection=cx={cx}:cy={cy}:k1={k1}:k2={k2}",
|
|
566
|
+
"-c:v", "libx264", "-preset", "slow", f"-crf", str(crf),
|
|
567
|
+
"-pix_fmt", "yuv420p",
|
|
568
|
+
"-c:a", "aac", "-b:a", "192k",
|
|
569
|
+
"-movflags", "+faststart",
|
|
570
|
+
outputFile
|
|
571
|
+
]
|
|
572
|
+
|
|
573
|
+
subprocess.run(cmd, check=True)
|
|
574
|
+
return outputFile
|
|
575
|
+
|
|
576
|
+
def correctLens(inputFile: str, outputFile: str = '',
|
|
577
|
+
k1: float = -0.1, k2: float = 0.05,
|
|
578
|
+
cx: float = 0.5, cy: float = 0.5,
|
|
579
|
+
crf: int = 20) -> str:
|
|
580
|
+
"""
|
|
581
|
+
Alias for correctFisheye
|
|
582
|
+
"""
|
|
583
|
+
return correctFisheye(inputFile, outputFile, k1, k2, cx, cy, crf)
|
|
584
|
+
|
|
531
585
|
def formatStringBlock(input_str: str, max_chars: int = 70) -> str:
|
|
532
586
|
'''
|
|
533
587
|
This function takes a string and formats it into a block of text
|
|
@@ -558,7 +612,6 @@ def formatStringBlock(input_str: str, max_chars: int = 70) -> str:
|
|
|
558
612
|
|
|
559
613
|
|
|
560
614
|
|
|
561
|
-
|
|
562
615
|
def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_connections: int = 5, v: bool = False) -> None:
|
|
563
616
|
if v:
|
|
564
617
|
print(f"\ndownloading {url}")
|
|
@@ -573,13 +626,17 @@ def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_co
|
|
|
573
626
|
if os.path.exists(save_fname):
|
|
574
627
|
if exists_action == 'skip':
|
|
575
628
|
if v:
|
|
576
|
-
print(f"
|
|
629
|
+
print(f"file exists, skipping: {save_fname}")
|
|
577
630
|
return
|
|
578
631
|
elif exists_action == 'overwrite':
|
|
579
632
|
os.remove(save_fname)
|
|
580
633
|
# 'resume' is handled below
|
|
581
634
|
|
|
582
|
-
# Get file size
|
|
635
|
+
# Get file size (suppress urllib3 warnings when v=False)
|
|
636
|
+
import urllib3
|
|
637
|
+
if not v:
|
|
638
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
639
|
+
|
|
583
640
|
response = requests.head(url)
|
|
584
641
|
file_size = int(response.headers.get('content-length', 0))
|
|
585
642
|
|
|
@@ -589,7 +646,7 @@ def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_co
|
|
|
589
646
|
initial_pos = os.path.getsize(save_fname)
|
|
590
647
|
if initial_pos >= file_size:
|
|
591
648
|
if v:
|
|
592
|
-
print(f"
|
|
649
|
+
print(f"file already completed: {save_fname}")
|
|
593
650
|
return
|
|
594
651
|
|
|
595
652
|
# Calculate chunk sizes
|
|
@@ -609,14 +666,22 @@ def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_co
|
|
|
609
666
|
executor.submit(downloadChunk, url, start, end, temp_files[i])
|
|
610
667
|
)
|
|
611
668
|
|
|
612
|
-
# Wait for all downloads to complete with progress bar
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
669
|
+
# Wait for all downloads to complete with progress bar (conditionally show progress)
|
|
670
|
+
if v:
|
|
671
|
+
with tqdm(total=file_size-initial_pos, initial=initial_pos, unit='B',
|
|
672
|
+
unit_scale=True, desc=fname) as pbar:
|
|
673
|
+
completed = initial_pos
|
|
674
|
+
while completed < file_size:
|
|
675
|
+
current = sum(os.path.getsize(f) for f in temp_files if os.path.exists(f))
|
|
676
|
+
pbar.update(current - completed)
|
|
677
|
+
completed = current
|
|
678
|
+
else:
|
|
679
|
+
# Wait silently without progress bar
|
|
680
|
+
while True:
|
|
617
681
|
current = sum(os.path.getsize(f) for f in temp_files if os.path.exists(f))
|
|
618
|
-
|
|
619
|
-
|
|
682
|
+
if current >= file_size - initial_pos:
|
|
683
|
+
break
|
|
684
|
+
time.sleep(0.1)
|
|
620
685
|
|
|
621
686
|
# Merge chunks
|
|
622
687
|
with open(save_fname, 'ab' if initial_pos > 0 else 'wb') as outfile:
|
|
@@ -627,6 +692,77 @@ def downloadFile(url: str, save_path: str, exists_action: str = 'resume', num_co
|
|
|
627
692
|
os.remove(temp_file)
|
|
628
693
|
|
|
629
694
|
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def createPolygonFromOuterPoints(pointsGdf: geopandas.GeoDataFrame, alpha: float = 1.6, keepHoles: bool = False) -> geopandas.GeoDataFrame:
|
|
698
|
+
"""
|
|
699
|
+
Concave hull (alpha-shape) from points.
|
|
700
|
+
alpha: larger -> tighter/more detailed; too large can fragment.
|
|
701
|
+
keepHoles: keep interior holes if True, otherwise drop them.
|
|
702
|
+
"""
|
|
703
|
+
|
|
704
|
+
pointsGdf = pointsGdf[pointsGdf.geometry.type.eq("Point") & pointsGdf.geometry.notna()]
|
|
705
|
+
if pointsGdf.empty or pointsGdf.geometry.nunique() < 3:
|
|
706
|
+
raise ValueError("need at least three distinct points")
|
|
707
|
+
|
|
708
|
+
# coordinates (N, 2)
|
|
709
|
+
coords = numpy.array([(g.x, g.y) for g in pointsGdf.geometry])
|
|
710
|
+
|
|
711
|
+
tri = scipy.spatial.Delaunay(coords)
|
|
712
|
+
simplices = tri.simplices # (M, 3) indices into coords
|
|
713
|
+
triPts = coords[simplices] # (M, 3, 2)
|
|
714
|
+
|
|
715
|
+
# side lengths
|
|
716
|
+
a = numpy.linalg.norm(triPts[:, 1] - triPts[:, 2], axis=1)
|
|
717
|
+
b = numpy.linalg.norm(triPts[:, 0] - triPts[:, 2], axis=1)
|
|
718
|
+
c = numpy.linalg.norm(triPts[:, 0] - triPts[:, 1], axis=1)
|
|
719
|
+
|
|
720
|
+
s = (a + b + c) / 2.0
|
|
721
|
+
# heron area, guard against tiny/negative due to fp error
|
|
722
|
+
areaSq = numpy.maximum(s * (s - a) * (s - b) * (s - c), 0.0)
|
|
723
|
+
area = numpy.sqrt(areaSq)
|
|
724
|
+
valid = area > 0.0
|
|
725
|
+
if not numpy.any(valid):
|
|
726
|
+
hull = pointsGdf.unary_union.convex_hull
|
|
727
|
+
return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
|
|
728
|
+
|
|
729
|
+
circumradius = (a * b * c) / (4.0 * area)
|
|
730
|
+
keep = valid & (circumradius < (1.0 / alpha))
|
|
731
|
+
keptSimplices = simplices[keep]
|
|
732
|
+
if keptSimplices.size == 0:
|
|
733
|
+
hull = pointsGdf.unary_union.convex_hull
|
|
734
|
+
return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
|
|
735
|
+
|
|
736
|
+
# count triangle edges; boundary edges appear exactly once
|
|
737
|
+
edgeCounts: dict[tuple[int, int], int] = {}
|
|
738
|
+
for i0, i1, i2 in keptSimplices:
|
|
739
|
+
for e in ((i0, i1), (i1, i2), (i2, i0)):
|
|
740
|
+
key = (e[0], e[1]) if e[0] < e[1] else (e[1], e[0])
|
|
741
|
+
edgeCounts[key] = edgeCounts.get(key, 0) + 1
|
|
742
|
+
|
|
743
|
+
boundaryLines = [
|
|
744
|
+
LineString([coords[i], coords[j]])
|
|
745
|
+
for (i, j), count in edgeCounts.items()
|
|
746
|
+
if count == 1
|
|
747
|
+
]
|
|
748
|
+
if not boundaryLines:
|
|
749
|
+
hull = pointsGdf.unary_union.convex_hull
|
|
750
|
+
return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
|
|
751
|
+
|
|
752
|
+
polygons = list(polygonize(boundaryLines))
|
|
753
|
+
if not polygons:
|
|
754
|
+
hull = pointsGdf.unary_union.convex_hull
|
|
755
|
+
return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[hull], crs=pointsGdf.crs)
|
|
756
|
+
|
|
757
|
+
merged = unary_union(polygons)
|
|
758
|
+
if isinstance(merged, MultiPolygon):
|
|
759
|
+
merged = max(merged.geoms, key=lambda g: g.area)
|
|
760
|
+
if not keepHoles and isinstance(merged, Polygon):
|
|
761
|
+
merged = Polygon(merged.exterior)
|
|
762
|
+
|
|
763
|
+
return geopandas.GeoDataFrame({"name": ["outerBoundary"]}, geometry=[merged], crs=pointsGdf.crs)
|
|
764
|
+
|
|
765
|
+
|
|
630
766
|
def mergeRasterTiles(tileList:list, outFile:str) -> str:
|
|
631
767
|
'''
|
|
632
768
|
Merge raster tiles into one raster file
|
|
@@ -977,6 +1113,116 @@ def extractCompressedFile(inputFile: str, outputDir: str, v: bool = False) -> No
|
|
|
977
1113
|
"""
|
|
978
1114
|
uncompress(inputFile, outputDir, v)
|
|
979
1115
|
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
def rasterizeGDF(gdf: geopandas.GeoDataFrame, valueField: str, outRasterFN: str, resolution: float, isCOG: bool = True, allTouched: bool = False, profileOverrides: dict | None = None) -> str:
|
|
1119
|
+
"""
|
|
1120
|
+
Rasterize a GeoDataFrame to GeoTIFF/COG.
|
|
1121
|
+
|
|
1122
|
+
Parameters
|
|
1123
|
+
----------
|
|
1124
|
+
gdf : geopandas.GeoDataFrame
|
|
1125
|
+
valueField : str
|
|
1126
|
+
Column to burn as pixel values.
|
|
1127
|
+
outRasterFN : str
|
|
1128
|
+
resolution : float
|
|
1129
|
+
Pixel size in CRS units (assumes a projected CRS).
|
|
1130
|
+
isCOG : bool, default True
|
|
1131
|
+
If True, writes Cloud-Optimized GeoTIFF. Otherwise plain GeoTIFF.
|
|
1132
|
+
allTouched : bool, default False
|
|
1133
|
+
Pass-through to rasterio.features.rasterize.
|
|
1134
|
+
profileOverrides : dict | None
|
|
1135
|
+
Extra GDAL profile options (e.g., {"compress": "LZW"}). Overrides defaults.
|
|
1136
|
+
|
|
1137
|
+
Returns
|
|
1138
|
+
-------
|
|
1139
|
+
str
|
|
1140
|
+
The path written to (outRasterFN).
|
|
1141
|
+
"""
|
|
1142
|
+
# basic checks
|
|
1143
|
+
if gdf is None or len(gdf) == 0 or gdf.geometry.isna().all():
|
|
1144
|
+
raise ValueError("gdf is empty or has no valid geometries.")
|
|
1145
|
+
if gdf.crs is None:
|
|
1146
|
+
raise ValueError("gdf must have a defined CRS.")
|
|
1147
|
+
if resolution <= 0:
|
|
1148
|
+
raise ValueError("resolution must be > 0.")
|
|
1149
|
+
if valueField not in gdf.columns:
|
|
1150
|
+
raise ValueError(f"valueField '{valueField}' not found in gdf.")
|
|
1151
|
+
|
|
1152
|
+
# compute raster shape + transform
|
|
1153
|
+
bounds = gdf.total_bounds # (minx, miny, maxx, maxy)
|
|
1154
|
+
width = int(numpy.ceil((bounds[2] - bounds[0]) / float(resolution)))
|
|
1155
|
+
height = int(numpy.ceil((bounds[3] - bounds[1]) / float(resolution)))
|
|
1156
|
+
if width < 1 or height < 1:
|
|
1157
|
+
raise ValueError("computed raster dimensions are invalid (check resolution and bounds).")
|
|
1158
|
+
|
|
1159
|
+
transform = from_bounds(bounds[0], bounds[1], bounds[2], bounds[3], width, height)
|
|
1160
|
+
|
|
1161
|
+
# infer dtype + nodata
|
|
1162
|
+
pandasDtype = gdf[valueField].dtype
|
|
1163
|
+
if numpy.issubdtype(pandasDtype, numpy.floating):
|
|
1164
|
+
dtype = numpy.float32
|
|
1165
|
+
nodata = numpy.nan
|
|
1166
|
+
fillValue = numpy.nan
|
|
1167
|
+
elif numpy.issubdtype(pandasDtype, numpy.bool_):
|
|
1168
|
+
dtype = numpy.uint8
|
|
1169
|
+
nodata = 255 # sentinel for bool raster
|
|
1170
|
+
fillValue = nodata
|
|
1171
|
+
else:
|
|
1172
|
+
dtype = numpy.int32
|
|
1173
|
+
nodata = -9999
|
|
1174
|
+
fillValue = nodata
|
|
1175
|
+
|
|
1176
|
+
# prefill target array
|
|
1177
|
+
raster = numpy.full((height, width), fillValue, dtype=dtype)
|
|
1178
|
+
|
|
1179
|
+
# build shapes generator (ensure python scalars)
|
|
1180
|
+
shapes = ((geom, (None if numpy.isnan(val) else val) if isinstance(val, float) else int(val) if numpy.issubdtype(type(val), numpy.integer) else float(val))
|
|
1181
|
+
for geom, val in zip(gdf.geometry, gdf[valueField]))
|
|
1182
|
+
|
|
1183
|
+
# burn
|
|
1184
|
+
features.rasterize(
|
|
1185
|
+
shapes=shapes,
|
|
1186
|
+
out_shape=raster.shape,
|
|
1187
|
+
transform=transform,
|
|
1188
|
+
fill=fillValue,
|
|
1189
|
+
out=raster,
|
|
1190
|
+
all_touched=allTouched,
|
|
1191
|
+
dtype=dtype
|
|
1192
|
+
)
|
|
1193
|
+
|
|
1194
|
+
# default profile settings
|
|
1195
|
+
profile = {
|
|
1196
|
+
"driver": "COG" if isCOG else "GTiff",
|
|
1197
|
+
"height": raster.shape[0],
|
|
1198
|
+
"width": raster.shape[1],
|
|
1199
|
+
"count": 1,
|
|
1200
|
+
"dtype": raster.dtype,
|
|
1201
|
+
"crs": gdf.crs,
|
|
1202
|
+
"transform": transform,
|
|
1203
|
+
"nodata": nodata,
|
|
1204
|
+
}
|
|
1205
|
+
# sane compression defaults
|
|
1206
|
+
if isCOG:
|
|
1207
|
+
profile.setdefault("compress", "LZW")
|
|
1208
|
+
profile.setdefault("blocksize", 512) # GDAL COG option via rasterio
|
|
1209
|
+
# overviews are handled by the COG driver
|
|
1210
|
+
else:
|
|
1211
|
+
profile.setdefault("compress", "LZW"),
|
|
1212
|
+
profile.setdefault("tiled", True)
|
|
1213
|
+
profile.setdefault("blockxsize", 512)
|
|
1214
|
+
profile.setdefault("blockysize", 512)
|
|
1215
|
+
|
|
1216
|
+
if profileOverrides:
|
|
1217
|
+
profile.update(profileOverrides)
|
|
1218
|
+
|
|
1219
|
+
# write
|
|
1220
|
+
with rasterio.open(outRasterFN, "w", **profile) as dst:
|
|
1221
|
+
dst.write(raster, 1)
|
|
1222
|
+
|
|
1223
|
+
return outRasterFN
|
|
1224
|
+
|
|
1225
|
+
|
|
980
1226
|
def moveDirectory(srcDir:str, destDir:str, v:bool = False) -> bool:
|
|
981
1227
|
'''
|
|
982
1228
|
this function moves all files from srcDir to destDir
|
|
@@ -1588,9 +1834,9 @@ def showProgress(count: int, end: int, message: str, barLength: int = 100) -> No
|
|
|
1588
1834
|
percentStr = f'{percent:03.1f}'
|
|
1589
1835
|
filled = int(barLength * count / end)
|
|
1590
1836
|
bar = '█' * filled + '░' * (barLength - filled)
|
|
1591
|
-
print(f'\r{message} |{bar}| {
|
|
1837
|
+
print(f'\r{message} |{bar}| {percentStr}% [{count}/{end}]', end='', flush=True)
|
|
1592
1838
|
if count == end:
|
|
1593
|
-
print(f'\r{message} |{bar}| {
|
|
1839
|
+
print(f'\r{message} |{bar}| {percentStr}% [{count}/{end}] ', end='', flush=True)
|
|
1594
1840
|
print()
|
|
1595
1841
|
|
|
1596
1842
|
|
|
@@ -2023,7 +2269,7 @@ def getTimeseriesStats(data:pandas.DataFrame, observed:Optional[str] = None, sim
|
|
|
2023
2269
|
|
|
2024
2270
|
return stats
|
|
2025
2271
|
|
|
2026
|
-
def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None) -> Optional[pandas.DataFrame]:
|
|
2272
|
+
def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optional[int] = None, gis_id: Optional[int] = None, name: Optional[str] = None, coerceNumeric: bool = True) -> Optional[pandas.DataFrame]:
|
|
2027
2273
|
'''
|
|
2028
2274
|
Read SWAT+ output files and return a pandas DataFrame with proper date handling
|
|
2029
2275
|
and optional filtering capabilities.
|
|
@@ -2085,8 +2331,15 @@ def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optio
|
|
|
2085
2331
|
# Convert all columns to numeric except 'name' (which is string)
|
|
2086
2332
|
for col in df.columns:
|
|
2087
2333
|
if col != 'name':
|
|
2088
|
-
|
|
2334
|
+
if coerceNumeric:
|
|
2335
|
+
df[col] = pandas.to_numeric(df[col], errors='coerce')
|
|
2089
2336
|
|
|
2337
|
+
if not coerceNumeric:
|
|
2338
|
+
# If not coercing to numeric, ensure date columns are numeric
|
|
2339
|
+
for mandatoryCol in ['yr', 'mon', 'day', 'gis_id']:
|
|
2340
|
+
if mandatoryCol in df.columns:
|
|
2341
|
+
df[mandatoryCol] = pandas.to_numeric(df[mandatoryCol], errors='coerce')
|
|
2342
|
+
|
|
2090
2343
|
# Create date column from yr, mon, day
|
|
2091
2344
|
try:
|
|
2092
2345
|
df['date'] = pandas.to_datetime(pandas.DataFrame({'year': df.yr, 'month': df.mon, 'day': df.day}))
|
|
@@ -2126,4 +2379,5 @@ def readSWATPlusOutputs(filePath: str, column: Optional[str] = None, unit: Optio
|
|
|
2126
2379
|
|
|
2127
2380
|
return df
|
|
2128
2381
|
|
|
2382
|
+
|
|
2129
2383
|
ignoreWarnings()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ccfx
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: This package simplifies regular common actions for quick prototyping in a user friendly way
|
|
5
5
|
Author-email: Celray James CHAWANDA <celray@chawanda.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -26,6 +26,9 @@ Requires-Dist: mutagen
|
|
|
26
26
|
Requires-Dist: requests
|
|
27
27
|
Requires-Dist: tqdm
|
|
28
28
|
Requires-Dist: pillow
|
|
29
|
+
Requires-Dist: scipy
|
|
30
|
+
Requires-Dist: rasterio
|
|
31
|
+
Requires-Dist: matplotlib
|
|
29
32
|
Dynamic: license-file
|
|
30
33
|
|
|
31
34
|
# ccfx
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
ccfx/__init__.py,sha256=UK62VcGS84SJyGVg1bK4FltZj7OkpdoyhoFWeXcKsX0,144
|
|
2
|
-
ccfx/ccfx.py,sha256=
|
|
2
|
+
ccfx/ccfx.py,sha256=st_zG-KXcqM9K2HjyontOwrR8FpZGbSZyHz0OvPHjoU,86682
|
|
3
3
|
ccfx/excel.py,sha256=vm_cm4huKKx4_Nstr5neJzhBLmoZjg8qxjzz4hcF5hg,4754
|
|
4
4
|
ccfx/mssqlConnection.py,sha256=C3HxzgZHmHy_de9EbMaXzR8NrkJxwHc8a00qzxQu_gs,8984
|
|
5
5
|
ccfx/sqliteConnection.py,sha256=pOT9BBEAcm2kmoS0yBkUi4m9srQVe62J4xG5bnddvis,16207
|
|
6
6
|
ccfx/word.py,sha256=AGa64jX5Zl5qotZh5L0QmrsjTnktIBhmj_ByRKZ88vw,3061
|
|
7
|
-
ccfx-1.0.
|
|
8
|
-
ccfx-1.0.
|
|
9
|
-
ccfx-1.0.
|
|
10
|
-
ccfx-1.0.
|
|
11
|
-
ccfx-1.0.
|
|
7
|
+
ccfx-1.1.0.dist-info/licenses/LICENSE,sha256=EuxaawJg_OOCLfikkCGgfXPZmxR-x_5PH7_2e9M-3eA,1099
|
|
8
|
+
ccfx-1.1.0.dist-info/METADATA,sha256=hi23r9sqTM-JPXyRrkuGG-0yamVxPCZeJ1qNCPMB5I0,11353
|
|
9
|
+
ccfx-1.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
ccfx-1.1.0.dist-info/top_level.txt,sha256=_cSvSA1WX2K8TgoV3iBJUdUZZqMKJbOPLNnKLYSLHaw,5
|
|
11
|
+
ccfx-1.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|